##// END OF EJS Templates
permissions: fixed missing user info on global and repository permissions pages.
dan -
r4418:d143e1d9 default
parent child Browse files
Show More
@@ -1,2041 +1,2041 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 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27 import base64
28 28
29 29 import os
30 30 import random
31 31 import hashlib
32 32 import StringIO
33 33 import textwrap
34 34 import urllib
35 35 import math
36 36 import logging
37 37 import re
38 38 import time
39 39 import string
40 40 import hashlib
41 41 from collections import OrderedDict
42 42
43 43 import pygments
44 44 import itertools
45 45 import fnmatch
46 46 import bleach
47 47
48 48 from pyramid import compat
49 49 from datetime import datetime
50 50 from functools import partial
51 51 from pygments.formatters.html import HtmlFormatter
52 52 from pygments.lexers import (
53 53 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
54 54
55 55 from pyramid.threadlocal import get_current_request
56 56
57 57 from webhelpers2.html import literal, HTML, escape
58 58 from webhelpers2.html._autolink import _auto_link_urls
59 59 from webhelpers2.html.tools import (
60 60 button_to, highlight, js_obfuscate, strip_links, strip_tags)
61 61
62 62 from webhelpers2.text import (
63 63 chop_at, collapse, convert_accented_entities,
64 64 convert_misc_entities, lchop, plural, rchop, remove_formatting,
65 65 replace_whitespace, urlify, truncate, wrap_paragraphs)
66 66 from webhelpers2.date import time_ago_in_words
67 67
68 68 from webhelpers2.html.tags import (
69 69 _input, NotGiven, _make_safe_id_component as safeid,
70 70 form as insecure_form,
71 71 auto_discovery_link, checkbox, end_form, file,
72 72 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
73 73 select as raw_select, stylesheet_link, submit, text, password, textarea,
74 74 ul, radio, Options)
75 75
76 76 from webhelpers2.number import format_byte_size
77 77
78 78 from rhodecode.lib.action_parser import action_parser
79 79 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
80 80 from rhodecode.lib.ext_json import json
81 81 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
82 82 from rhodecode.lib.utils2 import (
83 83 str2bool, safe_unicode, safe_str,
84 84 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
85 85 AttributeDict, safe_int, md5, md5_safe, get_host_info)
86 86 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
87 87 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
88 88 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
89 89 from rhodecode.lib.index.search_utils import get_matching_line_offsets
90 90 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
91 91 from rhodecode.model.changeset_status import ChangesetStatusModel
92 92 from rhodecode.model.db import Permission, User, Repository
93 93 from rhodecode.model.repo_group import RepoGroupModel
94 94 from rhodecode.model.settings import IssueTrackerSettingsModel
95 95
96 96
97 97 log = logging.getLogger(__name__)
98 98
99 99
100 100 DEFAULT_USER = User.DEFAULT_USER
101 101 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
102 102
103 103
104 104 def asset(path, ver=None, **kwargs):
105 105 """
106 106 Helper to generate a static asset file path for rhodecode assets
107 107
108 108 eg. h.asset('images/image.png', ver='3923')
109 109
110 110 :param path: path of asset
111 111 :param ver: optional version query param to append as ?ver=
112 112 """
113 113 request = get_current_request()
114 114 query = {}
115 115 query.update(kwargs)
116 116 if ver:
117 117 query = {'ver': ver}
118 118 return request.static_path(
119 119 'rhodecode:public/{}'.format(path), _query=query)
120 120
121 121
122 122 default_html_escape_table = {
123 123 ord('&'): u'&amp;',
124 124 ord('<'): u'&lt;',
125 125 ord('>'): u'&gt;',
126 126 ord('"'): u'&quot;',
127 127 ord("'"): u'&#39;',
128 128 }
129 129
130 130
131 131 def html_escape(text, html_escape_table=default_html_escape_table):
132 132 """Produce entities within text."""
133 133 return text.translate(html_escape_table)
134 134
135 135
136 136 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
137 137 """
138 138 Truncate string ``s`` at the first occurrence of ``sub``.
139 139
140 140 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
141 141 """
142 142 suffix_if_chopped = suffix_if_chopped or ''
143 143 pos = s.find(sub)
144 144 if pos == -1:
145 145 return s
146 146
147 147 if inclusive:
148 148 pos += len(sub)
149 149
150 150 chopped = s[:pos]
151 151 left = s[pos:].strip()
152 152
153 153 if left and suffix_if_chopped:
154 154 chopped += suffix_if_chopped
155 155
156 156 return chopped
157 157
158 158
159 159 def shorter(text, size=20, prefix=False):
160 160 postfix = '...'
161 161 if len(text) > size:
162 162 if prefix:
163 163 # shorten in front
164 164 return postfix + text[-(size - len(postfix)):]
165 165 else:
166 166 return text[:size - len(postfix)] + postfix
167 167 return text
168 168
169 169
170 170 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
171 171 """
172 172 Reset button
173 173 """
174 174 return _input(type, name, value, id, attrs)
175 175
176 176
177 177 def select(name, selected_values, options, id=NotGiven, **attrs):
178 178
179 179 if isinstance(options, (list, tuple)):
180 180 options_iter = options
181 181 # Handle old value,label lists ... where value also can be value,label lists
182 182 options = Options()
183 183 for opt in options_iter:
184 184 if isinstance(opt, tuple) and len(opt) == 2:
185 185 value, label = opt
186 186 elif isinstance(opt, basestring):
187 187 value = label = opt
188 188 else:
189 189 raise ValueError('invalid select option type %r' % type(opt))
190 190
191 191 if isinstance(value, (list, tuple)):
192 192 option_group = options.add_optgroup(label)
193 193 for opt2 in value:
194 194 if isinstance(opt2, tuple) and len(opt2) == 2:
195 195 group_value, group_label = opt2
196 196 elif isinstance(opt2, basestring):
197 197 group_value = group_label = opt2
198 198 else:
199 199 raise ValueError('invalid select option type %r' % type(opt2))
200 200
201 201 option_group.add_option(group_label, group_value)
202 202 else:
203 203 options.add_option(label, value)
204 204
205 205 return raw_select(name, selected_values, options, id=id, **attrs)
206 206
207 207
208 208 def branding(name, length=40):
209 209 return truncate(name, length, indicator="")
210 210
211 211
212 212 def FID(raw_id, path):
213 213 """
214 214 Creates a unique ID for filenode based on it's hash of path and commit
215 215 it's safe to use in urls
216 216
217 217 :param raw_id:
218 218 :param path:
219 219 """
220 220
221 221 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
222 222
223 223
224 224 class _GetError(object):
225 225 """Get error from form_errors, and represent it as span wrapped error
226 226 message
227 227
228 228 :param field_name: field to fetch errors for
229 229 :param form_errors: form errors dict
230 230 """
231 231
232 232 def __call__(self, field_name, form_errors):
233 233 tmpl = """<span class="error_msg">%s</span>"""
234 234 if form_errors and field_name in form_errors:
235 235 return literal(tmpl % form_errors.get(field_name))
236 236
237 237
238 238 get_error = _GetError()
239 239
240 240
241 241 class _ToolTip(object):
242 242
243 243 def __call__(self, tooltip_title, trim_at=50):
244 244 """
245 245 Special function just to wrap our text into nice formatted
246 246 autowrapped text
247 247
248 248 :param tooltip_title:
249 249 """
250 250 tooltip_title = escape(tooltip_title)
251 251 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
252 252 return tooltip_title
253 253
254 254
255 255 tooltip = _ToolTip()
256 256
257 257 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
258 258
259 259
260 260 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
261 261 limit_items=False, linkify_last_item=False, hide_last_item=False,
262 262 copy_path_icon=True):
263 263 if isinstance(file_path, str):
264 264 file_path = safe_unicode(file_path)
265 265
266 266 if at_ref:
267 267 route_qry = {'at': at_ref}
268 268 default_landing_ref = at_ref or landing_ref_name or commit_id
269 269 else:
270 270 route_qry = None
271 271 default_landing_ref = commit_id
272 272
273 273 # first segment is a `HOME` link to repo files root location
274 274 root_name = literal(u'<i class="icon-home"></i>')
275 275
276 276 url_segments = [
277 277 link_to(
278 278 root_name,
279 279 repo_files_by_ref_url(
280 280 repo_name,
281 281 repo_type,
282 282 f_path=None, # None here is a special case for SVN repos,
283 283 # that won't prefix with a ref
284 284 ref_name=default_landing_ref,
285 285 commit_id=commit_id,
286 286 query=route_qry
287 287 )
288 288 )]
289 289
290 290 path_segments = file_path.split('/')
291 291 last_cnt = len(path_segments) - 1
292 292 for cnt, segment in enumerate(path_segments):
293 293 if not segment:
294 294 continue
295 295 segment_html = escape(segment)
296 296
297 297 last_item = cnt == last_cnt
298 298
299 299 if last_item and hide_last_item:
300 300 # iterate over and hide last element
301 301 continue
302 302
303 303 if last_item and linkify_last_item is False:
304 304 # plain version
305 305 url_segments.append(segment_html)
306 306 else:
307 307 url_segments.append(
308 308 link_to(
309 309 segment_html,
310 310 repo_files_by_ref_url(
311 311 repo_name,
312 312 repo_type,
313 313 f_path='/'.join(path_segments[:cnt + 1]),
314 314 ref_name=default_landing_ref,
315 315 commit_id=commit_id,
316 316 query=route_qry
317 317 ),
318 318 ))
319 319
320 320 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
321 321 if limit_items and len(limited_url_segments) < len(url_segments):
322 322 url_segments = limited_url_segments
323 323
324 324 full_path = file_path
325 325 if copy_path_icon:
326 326 icon = files_icon.format(escape(full_path))
327 327 else:
328 328 icon = ''
329 329
330 330 if file_path == '':
331 331 return root_name
332 332 else:
333 333 return literal(' / '.join(url_segments) + icon)
334 334
335 335
336 336 def files_url_data(request):
337 337 matchdict = request.matchdict
338 338
339 339 if 'f_path' not in matchdict:
340 340 matchdict['f_path'] = ''
341 341
342 342 if 'commit_id' not in matchdict:
343 343 matchdict['commit_id'] = 'tip'
344 344
345 345 return json.dumps(matchdict)
346 346
347 347
348 348 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
349 349 _is_svn = is_svn(db_repo_type)
350 350 final_f_path = f_path
351 351
352 352 if _is_svn:
353 353 """
354 354 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
355 355 actually commit_id followed by the ref_name. This should be done only in case
356 356 This is a initial landing url, without additional paths.
357 357
358 358 like: /1000/tags/1.0.0/?at=tags/1.0.0
359 359 """
360 360
361 361 if ref_name and ref_name != 'tip':
362 362 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
363 363 # for SVN we only do this magic prefix if it's root, .eg landing revision
364 364 # of files link. If we are in the tree we don't need this since we traverse the url
365 365 # that has everything stored
366 366 if f_path in ['', '/']:
367 367 final_f_path = '/'.join([ref_name, f_path])
368 368
369 369 # SVN always needs a commit_id explicitly, without a named REF
370 370 default_commit_id = commit_id
371 371 else:
372 372 """
373 373 For git and mercurial we construct a new URL using the names instead of commit_id
374 374 like: /master/some_path?at=master
375 375 """
376 376 # We currently do not support branches with slashes
377 377 if '/' in ref_name:
378 378 default_commit_id = commit_id
379 379 else:
380 380 default_commit_id = ref_name
381 381
382 382 # sometimes we pass f_path as None, to indicate explicit no prefix,
383 383 # we translate it to string to not have None
384 384 final_f_path = final_f_path or ''
385 385
386 386 files_url = route_path(
387 387 'repo_files',
388 388 repo_name=db_repo_name,
389 389 commit_id=default_commit_id,
390 390 f_path=final_f_path,
391 391 _query=query
392 392 )
393 393 return files_url
394 394
395 395
396 396 def code_highlight(code, lexer, formatter, use_hl_filter=False):
397 397 """
398 398 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
399 399
400 400 If ``outfile`` is given and a valid file object (an object
401 401 with a ``write`` method), the result will be written to it, otherwise
402 402 it is returned as a string.
403 403 """
404 404 if use_hl_filter:
405 405 # add HL filter
406 406 from rhodecode.lib.index import search_utils
407 407 lexer.add_filter(search_utils.ElasticSearchHLFilter())
408 408 return pygments.format(pygments.lex(code, lexer), formatter)
409 409
410 410
411 411 class CodeHtmlFormatter(HtmlFormatter):
412 412 """
413 413 My code Html Formatter for source codes
414 414 """
415 415
416 416 def wrap(self, source, outfile):
417 417 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
418 418
419 419 def _wrap_code(self, source):
420 420 for cnt, it in enumerate(source):
421 421 i, t = it
422 422 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
423 423 yield i, t
424 424
425 425 def _wrap_tablelinenos(self, inner):
426 426 dummyoutfile = StringIO.StringIO()
427 427 lncount = 0
428 428 for t, line in inner:
429 429 if t:
430 430 lncount += 1
431 431 dummyoutfile.write(line)
432 432
433 433 fl = self.linenostart
434 434 mw = len(str(lncount + fl - 1))
435 435 sp = self.linenospecial
436 436 st = self.linenostep
437 437 la = self.lineanchors
438 438 aln = self.anchorlinenos
439 439 nocls = self.noclasses
440 440 if sp:
441 441 lines = []
442 442
443 443 for i in range(fl, fl + lncount):
444 444 if i % st == 0:
445 445 if i % sp == 0:
446 446 if aln:
447 447 lines.append('<a href="#%s%d" class="special">%*d</a>' %
448 448 (la, i, mw, i))
449 449 else:
450 450 lines.append('<span class="special">%*d</span>' % (mw, i))
451 451 else:
452 452 if aln:
453 453 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
454 454 else:
455 455 lines.append('%*d' % (mw, i))
456 456 else:
457 457 lines.append('')
458 458 ls = '\n'.join(lines)
459 459 else:
460 460 lines = []
461 461 for i in range(fl, fl + lncount):
462 462 if i % st == 0:
463 463 if aln:
464 464 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
465 465 else:
466 466 lines.append('%*d' % (mw, i))
467 467 else:
468 468 lines.append('')
469 469 ls = '\n'.join(lines)
470 470
471 471 # in case you wonder about the seemingly redundant <div> here: since the
472 472 # content in the other cell also is wrapped in a div, some browsers in
473 473 # some configurations seem to mess up the formatting...
474 474 if nocls:
475 475 yield 0, ('<table class="%stable">' % self.cssclass +
476 476 '<tr><td><div class="linenodiv" '
477 477 'style="background-color: #f0f0f0; padding-right: 10px">'
478 478 '<pre style="line-height: 125%">' +
479 479 ls + '</pre></div></td><td id="hlcode" class="code">')
480 480 else:
481 481 yield 0, ('<table class="%stable">' % self.cssclass +
482 482 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
483 483 ls + '</pre></div></td><td id="hlcode" class="code">')
484 484 yield 0, dummyoutfile.getvalue()
485 485 yield 0, '</td></tr></table>'
486 486
487 487
488 488 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
489 489 def __init__(self, **kw):
490 490 # only show these line numbers if set
491 491 self.only_lines = kw.pop('only_line_numbers', [])
492 492 self.query_terms = kw.pop('query_terms', [])
493 493 self.max_lines = kw.pop('max_lines', 5)
494 494 self.line_context = kw.pop('line_context', 3)
495 495 self.url = kw.pop('url', None)
496 496
497 497 super(CodeHtmlFormatter, self).__init__(**kw)
498 498
499 499 def _wrap_code(self, source):
500 500 for cnt, it in enumerate(source):
501 501 i, t = it
502 502 t = '<pre>%s</pre>' % t
503 503 yield i, t
504 504
505 505 def _wrap_tablelinenos(self, inner):
506 506 yield 0, '<table class="code-highlight %stable">' % self.cssclass
507 507
508 508 last_shown_line_number = 0
509 509 current_line_number = 1
510 510
511 511 for t, line in inner:
512 512 if not t:
513 513 yield t, line
514 514 continue
515 515
516 516 if current_line_number in self.only_lines:
517 517 if last_shown_line_number + 1 != current_line_number:
518 518 yield 0, '<tr>'
519 519 yield 0, '<td class="line">...</td>'
520 520 yield 0, '<td id="hlcode" class="code"></td>'
521 521 yield 0, '</tr>'
522 522
523 523 yield 0, '<tr>'
524 524 if self.url:
525 525 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
526 526 self.url, current_line_number, current_line_number)
527 527 else:
528 528 yield 0, '<td class="line"><a href="">%i</a></td>' % (
529 529 current_line_number)
530 530 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
531 531 yield 0, '</tr>'
532 532
533 533 last_shown_line_number = current_line_number
534 534
535 535 current_line_number += 1
536 536
537 537 yield 0, '</table>'
538 538
539 539
540 540 def hsv_to_rgb(h, s, v):
541 541 """ Convert hsv color values to rgb """
542 542
543 543 if s == 0.0:
544 544 return v, v, v
545 545 i = int(h * 6.0) # XXX assume int() truncates!
546 546 f = (h * 6.0) - i
547 547 p = v * (1.0 - s)
548 548 q = v * (1.0 - s * f)
549 549 t = v * (1.0 - s * (1.0 - f))
550 550 i = i % 6
551 551 if i == 0:
552 552 return v, t, p
553 553 if i == 1:
554 554 return q, v, p
555 555 if i == 2:
556 556 return p, v, t
557 557 if i == 3:
558 558 return p, q, v
559 559 if i == 4:
560 560 return t, p, v
561 561 if i == 5:
562 562 return v, p, q
563 563
564 564
565 565 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
566 566 """
567 567 Generator for getting n of evenly distributed colors using
568 568 hsv color and golden ratio. It always return same order of colors
569 569
570 570 :param n: number of colors to generate
571 571 :param saturation: saturation of returned colors
572 572 :param lightness: lightness of returned colors
573 573 :returns: RGB tuple
574 574 """
575 575
576 576 golden_ratio = 0.618033988749895
577 577 h = 0.22717784590367374
578 578
579 579 for _ in xrange(n):
580 580 h += golden_ratio
581 581 h %= 1
582 582 HSV_tuple = [h, saturation, lightness]
583 583 RGB_tuple = hsv_to_rgb(*HSV_tuple)
584 584 yield map(lambda x: str(int(x * 256)), RGB_tuple)
585 585
586 586
587 587 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
588 588 """
589 589 Returns a function which when called with an argument returns a unique
590 590 color for that argument, eg.
591 591
592 592 :param n: number of colors to generate
593 593 :param saturation: saturation of returned colors
594 594 :param lightness: lightness of returned colors
595 595 :returns: css RGB string
596 596
597 597 >>> color_hash = color_hasher()
598 598 >>> color_hash('hello')
599 599 'rgb(34, 12, 59)'
600 600 >>> color_hash('hello')
601 601 'rgb(34, 12, 59)'
602 602 >>> color_hash('other')
603 603 'rgb(90, 224, 159)'
604 604 """
605 605
606 606 color_dict = {}
607 607 cgenerator = unique_color_generator(
608 608 saturation=saturation, lightness=lightness)
609 609
610 610 def get_color_string(thing):
611 611 if thing in color_dict:
612 612 col = color_dict[thing]
613 613 else:
614 614 col = color_dict[thing] = cgenerator.next()
615 615 return "rgb(%s)" % (', '.join(col))
616 616
617 617 return get_color_string
618 618
619 619
620 620 def get_lexer_safe(mimetype=None, filepath=None):
621 621 """
622 622 Tries to return a relevant pygments lexer using mimetype/filepath name,
623 623 defaulting to plain text if none could be found
624 624 """
625 625 lexer = None
626 626 try:
627 627 if mimetype:
628 628 lexer = get_lexer_for_mimetype(mimetype)
629 629 if not lexer:
630 630 lexer = get_lexer_for_filename(filepath)
631 631 except pygments.util.ClassNotFound:
632 632 pass
633 633
634 634 if not lexer:
635 635 lexer = get_lexer_by_name('text')
636 636
637 637 return lexer
638 638
639 639
640 640 def get_lexer_for_filenode(filenode):
641 641 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
642 642 return lexer
643 643
644 644
645 645 def pygmentize(filenode, **kwargs):
646 646 """
647 647 pygmentize function using pygments
648 648
649 649 :param filenode:
650 650 """
651 651 lexer = get_lexer_for_filenode(filenode)
652 652 return literal(code_highlight(filenode.content, lexer,
653 653 CodeHtmlFormatter(**kwargs)))
654 654
655 655
656 656 def is_following_repo(repo_name, user_id):
657 657 from rhodecode.model.scm import ScmModel
658 658 return ScmModel().is_following_repo(repo_name, user_id)
659 659
660 660
661 661 class _Message(object):
662 662 """A message returned by ``Flash.pop_messages()``.
663 663
664 664 Converting the message to a string returns the message text. Instances
665 665 also have the following attributes:
666 666
667 667 * ``message``: the message text.
668 668 * ``category``: the category specified when the message was created.
669 669 """
670 670
671 671 def __init__(self, category, message, sub_data=None):
672 672 self.category = category
673 673 self.message = message
674 674 self.sub_data = sub_data or {}
675 675
676 676 def __str__(self):
677 677 return self.message
678 678
679 679 __unicode__ = __str__
680 680
681 681 def __html__(self):
682 682 return escape(safe_unicode(self.message))
683 683
684 684
685 685 class Flash(object):
686 686 # List of allowed categories. If None, allow any category.
687 687 categories = ["warning", "notice", "error", "success"]
688 688
689 689 # Default category if none is specified.
690 690 default_category = "notice"
691 691
692 692 def __init__(self, session_key="flash", categories=None,
693 693 default_category=None):
694 694 """
695 695 Instantiate a ``Flash`` object.
696 696
697 697 ``session_key`` is the key to save the messages under in the user's
698 698 session.
699 699
700 700 ``categories`` is an optional list which overrides the default list
701 701 of categories.
702 702
703 703 ``default_category`` overrides the default category used for messages
704 704 when none is specified.
705 705 """
706 706 self.session_key = session_key
707 707 if categories is not None:
708 708 self.categories = categories
709 709 if default_category is not None:
710 710 self.default_category = default_category
711 711 if self.categories and self.default_category not in self.categories:
712 712 raise ValueError(
713 713 "unrecognized default category %r" % (self.default_category,))
714 714
715 715 def pop_messages(self, session=None, request=None):
716 716 """
717 717 Return all accumulated messages and delete them from the session.
718 718
719 719 The return value is a list of ``Message`` objects.
720 720 """
721 721 messages = []
722 722
723 723 if not session:
724 724 if not request:
725 725 request = get_current_request()
726 726 session = request.session
727 727
728 728 # Pop the 'old' pylons flash messages. They are tuples of the form
729 729 # (category, message)
730 730 for cat, msg in session.pop(self.session_key, []):
731 731 messages.append(_Message(cat, msg))
732 732
733 733 # Pop the 'new' pyramid flash messages for each category as list
734 734 # of strings.
735 735 for cat in self.categories:
736 736 for msg in session.pop_flash(queue=cat):
737 737 sub_data = {}
738 738 if hasattr(msg, 'rsplit'):
739 739 flash_data = msg.rsplit('|DELIM|', 1)
740 740 org_message = flash_data[0]
741 741 if len(flash_data) > 1:
742 742 sub_data = json.loads(flash_data[1])
743 743 else:
744 744 org_message = msg
745 745
746 746 messages.append(_Message(cat, org_message, sub_data=sub_data))
747 747
748 748 # Map messages from the default queue to the 'notice' category.
749 749 for msg in session.pop_flash():
750 750 messages.append(_Message('notice', msg))
751 751
752 752 session.save()
753 753 return messages
754 754
755 755 def json_alerts(self, session=None, request=None):
756 756 payloads = []
757 757 messages = flash.pop_messages(session=session, request=request) or []
758 758 for message in messages:
759 759 payloads.append({
760 760 'message': {
761 761 'message': u'{}'.format(message.message),
762 762 'level': message.category,
763 763 'force': True,
764 764 'subdata': message.sub_data
765 765 }
766 766 })
767 767 return json.dumps(payloads)
768 768
769 769 def __call__(self, message, category=None, ignore_duplicate=True,
770 770 session=None, request=None):
771 771
772 772 if not session:
773 773 if not request:
774 774 request = get_current_request()
775 775 session = request.session
776 776
777 777 session.flash(
778 778 message, queue=category, allow_duplicate=not ignore_duplicate)
779 779
780 780
781 781 flash = Flash()
782 782
783 783 #==============================================================================
784 784 # SCM FILTERS available via h.
785 785 #==============================================================================
786 786 from rhodecode.lib.vcs.utils import author_name, author_email
787 787 from rhodecode.lib.utils2 import age, age_from_seconds
788 788 from rhodecode.model.db import User, ChangesetStatus
789 789
790 790
791 791 email = author_email
792 792
793 793
794 794 def capitalize(raw_text):
795 795 return raw_text.capitalize()
796 796
797 797
798 798 def short_id(long_id):
799 799 return long_id[:12]
800 800
801 801
802 802 def hide_credentials(url):
803 803 from rhodecode.lib.utils2 import credentials_filter
804 804 return credentials_filter(url)
805 805
806 806
807 807 import pytz
808 808 import tzlocal
809 809 local_timezone = tzlocal.get_localzone()
810 810
811 811
812 812 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
813 813 title = value or format_date(datetime_iso)
814 814 tzinfo = '+00:00'
815 815
816 816 # detect if we have a timezone info, otherwise, add it
817 817 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
818 818 force_timezone = os.environ.get('RC_TIMEZONE', '')
819 819 if force_timezone:
820 820 force_timezone = pytz.timezone(force_timezone)
821 821 timezone = force_timezone or local_timezone
822 822 offset = timezone.localize(datetime_iso).strftime('%z')
823 823 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
824 824
825 825 return literal(
826 826 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
827 827 cls='tooltip' if tooltip else '',
828 828 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
829 829 title=title, dt=datetime_iso, tzinfo=tzinfo
830 830 ))
831 831
832 832
833 833 def _shorten_commit_id(commit_id, commit_len=None):
834 834 if commit_len is None:
835 835 request = get_current_request()
836 836 commit_len = request.call_context.visual.show_sha_length
837 837 return commit_id[:commit_len]
838 838
839 839
840 840 def show_id(commit, show_idx=None, commit_len=None):
841 841 """
842 842 Configurable function that shows ID
843 843 by default it's r123:fffeeefffeee
844 844
845 845 :param commit: commit instance
846 846 """
847 847 if show_idx is None:
848 848 request = get_current_request()
849 849 show_idx = request.call_context.visual.show_revision_number
850 850
851 851 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
852 852 if show_idx:
853 853 return 'r%s:%s' % (commit.idx, raw_id)
854 854 else:
855 855 return '%s' % (raw_id, )
856 856
857 857
858 858 def format_date(date):
859 859 """
860 860 use a standardized formatting for dates used in RhodeCode
861 861
862 862 :param date: date/datetime object
863 863 :return: formatted date
864 864 """
865 865
866 866 if date:
867 867 _fmt = "%a, %d %b %Y %H:%M:%S"
868 868 return safe_unicode(date.strftime(_fmt))
869 869
870 870 return u""
871 871
872 872
873 873 class _RepoChecker(object):
874 874
875 875 def __init__(self, backend_alias):
876 876 self._backend_alias = backend_alias
877 877
878 878 def __call__(self, repository):
879 879 if hasattr(repository, 'alias'):
880 880 _type = repository.alias
881 881 elif hasattr(repository, 'repo_type'):
882 882 _type = repository.repo_type
883 883 else:
884 884 _type = repository
885 885 return _type == self._backend_alias
886 886
887 887
888 888 is_git = _RepoChecker('git')
889 889 is_hg = _RepoChecker('hg')
890 890 is_svn = _RepoChecker('svn')
891 891
892 892
893 893 def get_repo_type_by_name(repo_name):
894 894 repo = Repository.get_by_repo_name(repo_name)
895 895 if repo:
896 896 return repo.repo_type
897 897
898 898
899 899 def is_svn_without_proxy(repository):
900 900 if is_svn(repository):
901 901 from rhodecode.model.settings import VcsSettingsModel
902 902 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
903 903 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
904 904 return False
905 905
906 906
907 907 def discover_user(author):
908 908 """
909 909 Tries to discover RhodeCode User based on the author string. Author string
910 910 is typically `FirstName LastName <email@address.com>`
911 911 """
912 912
913 913 # if author is already an instance use it for extraction
914 914 if isinstance(author, User):
915 915 return author
916 916
917 917 # Valid email in the attribute passed, see if they're in the system
918 918 _email = author_email(author)
919 919 if _email != '':
920 920 user = User.get_by_email(_email, case_insensitive=True, cache=True)
921 921 if user is not None:
922 922 return user
923 923
924 924 # Maybe it's a username, we try to extract it and fetch by username ?
925 925 _author = author_name(author)
926 926 user = User.get_by_username(_author, case_insensitive=True, cache=True)
927 927 if user is not None:
928 928 return user
929 929
930 930 return None
931 931
932 932
933 933 def email_or_none(author):
934 934 # extract email from the commit string
935 935 _email = author_email(author)
936 936
937 937 # If we have an email, use it, otherwise
938 938 # see if it contains a username we can get an email from
939 939 if _email != '':
940 940 return _email
941 941 else:
942 942 user = User.get_by_username(
943 943 author_name(author), case_insensitive=True, cache=True)
944 944
945 945 if user is not None:
946 946 return user.email
947 947
948 948 # No valid email, not a valid user in the system, none!
949 949 return None
950 950
951 951
952 952 def link_to_user(author, length=0, **kwargs):
953 953 user = discover_user(author)
954 954 # user can be None, but if we have it already it means we can re-use it
955 955 # in the person() function, so we save 1 intensive-query
956 956 if user:
957 957 author = user
958 958
959 959 display_person = person(author, 'username_or_name_or_email')
960 960 if length:
961 961 display_person = shorter(display_person, length)
962 962
963 if user:
963 if user and user.username != user.DEFAULT_USER:
964 964 return link_to(
965 965 escape(display_person),
966 966 route_path('user_profile', username=user.username),
967 967 **kwargs)
968 968 else:
969 969 return escape(display_person)
970 970
971 971
972 972 def link_to_group(users_group_name, **kwargs):
973 973 return link_to(
974 974 escape(users_group_name),
975 975 route_path('user_group_profile', user_group_name=users_group_name),
976 976 **kwargs)
977 977
978 978
979 979 def person(author, show_attr="username_and_name"):
980 980 user = discover_user(author)
981 981 if user:
982 982 return getattr(user, show_attr)
983 983 else:
984 984 _author = author_name(author)
985 985 _email = email(author)
986 986 return _author or _email
987 987
988 988
989 989 def author_string(email):
990 990 if email:
991 991 user = User.get_by_email(email, case_insensitive=True, cache=True)
992 992 if user:
993 993 if user.first_name or user.last_name:
994 994 return '%s %s &lt;%s&gt;' % (
995 995 user.first_name, user.last_name, email)
996 996 else:
997 997 return email
998 998 else:
999 999 return email
1000 1000 else:
1001 1001 return None
1002 1002
1003 1003
1004 1004 def person_by_id(id_, show_attr="username_and_name"):
1005 1005 # attr to return from fetched user
1006 1006 person_getter = lambda usr: getattr(usr, show_attr)
1007 1007
1008 1008 #maybe it's an ID ?
1009 1009 if str(id_).isdigit() or isinstance(id_, int):
1010 1010 id_ = int(id_)
1011 1011 user = User.get(id_)
1012 1012 if user is not None:
1013 1013 return person_getter(user)
1014 1014 return id_
1015 1015
1016 1016
1017 1017 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
1018 1018 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
1019 1019 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
1020 1020
1021 1021
1022 1022 tags_paterns = OrderedDict((
1023 1023 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
1024 1024 '<div class="metatag" tag="lang">\\2</div>')),
1025 1025
1026 1026 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1027 1027 '<div class="metatag" tag="see">see: \\1 </div>')),
1028 1028
1029 1029 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
1030 1030 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
1031 1031
1032 1032 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1033 1033 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
1034 1034
1035 1035 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
1036 1036 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
1037 1037
1038 1038 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
1039 1039 '<div class="metatag" tag="state \\1">\\1</div>')),
1040 1040
1041 1041 # label in grey
1042 1042 ('label', (re.compile(r'\[([a-z]+)\]'),
1043 1043 '<div class="metatag" tag="label">\\1</div>')),
1044 1044
1045 1045 # generic catch all in grey
1046 1046 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
1047 1047 '<div class="metatag" tag="generic">\\1</div>')),
1048 1048 ))
1049 1049
1050 1050
1051 1051 def extract_metatags(value):
1052 1052 """
1053 1053 Extract supported meta-tags from given text value
1054 1054 """
1055 1055 tags = []
1056 1056 if not value:
1057 1057 return tags, ''
1058 1058
1059 1059 for key, val in tags_paterns.items():
1060 1060 pat, replace_html = val
1061 1061 tags.extend([(key, x.group()) for x in pat.finditer(value)])
1062 1062 value = pat.sub('', value)
1063 1063
1064 1064 return tags, value
1065 1065
1066 1066
1067 1067 def style_metatag(tag_type, value):
1068 1068 """
1069 1069 converts tags from value into html equivalent
1070 1070 """
1071 1071 if not value:
1072 1072 return ''
1073 1073
1074 1074 html_value = value
1075 1075 tag_data = tags_paterns.get(tag_type)
1076 1076 if tag_data:
1077 1077 pat, replace_html = tag_data
1078 1078 # convert to plain `unicode` instead of a markup tag to be used in
1079 1079 # regex expressions. safe_unicode doesn't work here
1080 1080 html_value = pat.sub(replace_html, unicode(value))
1081 1081
1082 1082 return html_value
1083 1083
1084 1084
1085 1085 def bool2icon(value, show_at_false=True):
1086 1086 """
1087 1087 Returns boolean value of a given value, represented as html element with
1088 1088 classes that will represent icons
1089 1089
1090 1090 :param value: given value to convert to html node
1091 1091 """
1092 1092
1093 1093 if value: # does bool conversion
1094 1094 return HTML.tag('i', class_="icon-true", title='True')
1095 1095 else: # not true as bool
1096 1096 if show_at_false:
1097 1097 return HTML.tag('i', class_="icon-false", title='False')
1098 1098 return HTML.tag('i')
1099 1099
1100 1100 #==============================================================================
1101 1101 # PERMS
1102 1102 #==============================================================================
1103 1103 from rhodecode.lib.auth import (
1104 1104 HasPermissionAny, HasPermissionAll,
1105 1105 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1106 1106 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1107 1107 csrf_token_key, AuthUser)
1108 1108
1109 1109
1110 1110 #==============================================================================
1111 1111 # GRAVATAR URL
1112 1112 #==============================================================================
1113 1113 class InitialsGravatar(object):
1114 1114 def __init__(self, email_address, first_name, last_name, size=30,
1115 1115 background=None, text_color='#fff'):
1116 1116 self.size = size
1117 1117 self.first_name = first_name
1118 1118 self.last_name = last_name
1119 1119 self.email_address = email_address
1120 1120 self.background = background or self.str2color(email_address)
1121 1121 self.text_color = text_color
1122 1122
1123 1123 def get_color_bank(self):
1124 1124 """
1125 1125 returns a predefined list of colors that gravatars can use.
1126 1126 Those are randomized distinct colors that guarantee readability and
1127 1127 uniqueness.
1128 1128
1129 1129 generated with: http://phrogz.net/css/distinct-colors.html
1130 1130 """
1131 1131 return [
1132 1132 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1133 1133 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1134 1134 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1135 1135 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1136 1136 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1137 1137 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1138 1138 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1139 1139 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1140 1140 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1141 1141 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1142 1142 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1143 1143 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1144 1144 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1145 1145 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1146 1146 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1147 1147 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1148 1148 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1149 1149 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1150 1150 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1151 1151 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1152 1152 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1153 1153 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1154 1154 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1155 1155 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1156 1156 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1157 1157 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1158 1158 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1159 1159 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1160 1160 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1161 1161 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1162 1162 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1163 1163 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1164 1164 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1165 1165 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1166 1166 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1167 1167 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1168 1168 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1169 1169 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1170 1170 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1171 1171 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1172 1172 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1173 1173 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1174 1174 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1175 1175 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1176 1176 '#4f8c46', '#368dd9', '#5c0073'
1177 1177 ]
1178 1178
1179 1179 def rgb_to_hex_color(self, rgb_tuple):
1180 1180 """
1181 1181 Converts an rgb_tuple passed to an hex color.
1182 1182
1183 1183 :param rgb_tuple: tuple with 3 ints represents rgb color space
1184 1184 """
1185 1185 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1186 1186
1187 1187 def email_to_int_list(self, email_str):
1188 1188 """
1189 1189 Get every byte of the hex digest value of email and turn it to integer.
1190 1190 It's going to be always between 0-255
1191 1191 """
1192 1192 digest = md5_safe(email_str.lower())
1193 1193 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1194 1194
1195 1195 def pick_color_bank_index(self, email_str, color_bank):
1196 1196 return self.email_to_int_list(email_str)[0] % len(color_bank)
1197 1197
1198 1198 def str2color(self, email_str):
1199 1199 """
1200 1200 Tries to map in a stable algorithm an email to color
1201 1201
1202 1202 :param email_str:
1203 1203 """
1204 1204 color_bank = self.get_color_bank()
1205 1205 # pick position (module it's length so we always find it in the
1206 1206 # bank even if it's smaller than 256 values
1207 1207 pos = self.pick_color_bank_index(email_str, color_bank)
1208 1208 return color_bank[pos]
1209 1209
1210 1210 def normalize_email(self, email_address):
1211 1211 import unicodedata
1212 1212 # default host used to fill in the fake/missing email
1213 1213 default_host = u'localhost'
1214 1214
1215 1215 if not email_address:
1216 1216 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1217 1217
1218 1218 email_address = safe_unicode(email_address)
1219 1219
1220 1220 if u'@' not in email_address:
1221 1221 email_address = u'%s@%s' % (email_address, default_host)
1222 1222
1223 1223 if email_address.endswith(u'@'):
1224 1224 email_address = u'%s%s' % (email_address, default_host)
1225 1225
1226 1226 email_address = unicodedata.normalize('NFKD', email_address)\
1227 1227 .encode('ascii', 'ignore')
1228 1228 return email_address
1229 1229
1230 1230 def get_initials(self):
1231 1231 """
1232 1232 Returns 2 letter initials calculated based on the input.
1233 1233 The algorithm picks first given email address, and takes first letter
1234 1234 of part before @, and then the first letter of server name. In case
1235 1235 the part before @ is in a format of `somestring.somestring2` it replaces
1236 1236 the server letter with first letter of somestring2
1237 1237
1238 1238 In case function was initialized with both first and lastname, this
1239 1239 overrides the extraction from email by first letter of the first and
1240 1240 last name. We add special logic to that functionality, In case Full name
1241 1241 is compound, like Guido Von Rossum, we use last part of the last name
1242 1242 (Von Rossum) picking `R`.
1243 1243
1244 1244 Function also normalizes the non-ascii characters to they ascii
1245 1245 representation, eg Δ„ => A
1246 1246 """
1247 1247 import unicodedata
1248 1248 # replace non-ascii to ascii
1249 1249 first_name = unicodedata.normalize(
1250 1250 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1251 1251 last_name = unicodedata.normalize(
1252 1252 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1253 1253
1254 1254 # do NFKD encoding, and also make sure email has proper format
1255 1255 email_address = self.normalize_email(self.email_address)
1256 1256
1257 1257 # first push the email initials
1258 1258 prefix, server = email_address.split('@', 1)
1259 1259
1260 1260 # check if prefix is maybe a 'first_name.last_name' syntax
1261 1261 _dot_split = prefix.rsplit('.', 1)
1262 1262 if len(_dot_split) == 2 and _dot_split[1]:
1263 1263 initials = [_dot_split[0][0], _dot_split[1][0]]
1264 1264 else:
1265 1265 initials = [prefix[0], server[0]]
1266 1266
1267 1267 # then try to replace either first_name or last_name
1268 1268 fn_letter = (first_name or " ")[0].strip()
1269 1269 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1270 1270
1271 1271 if fn_letter:
1272 1272 initials[0] = fn_letter
1273 1273
1274 1274 if ln_letter:
1275 1275 initials[1] = ln_letter
1276 1276
1277 1277 return ''.join(initials).upper()
1278 1278
1279 1279 def get_img_data_by_type(self, font_family, img_type):
1280 1280 default_user = """
1281 1281 <svg xmlns="http://www.w3.org/2000/svg"
1282 1282 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1283 1283 viewBox="-15 -10 439.165 429.164"
1284 1284
1285 1285 xml:space="preserve"
1286 1286 style="background:{background};" >
1287 1287
1288 1288 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1289 1289 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1290 1290 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1291 1291 168.596,153.916,216.671,
1292 1292 204.583,216.671z" fill="{text_color}"/>
1293 1293 <path d="M407.164,374.717L360.88,
1294 1294 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1295 1295 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1296 1296 15.366-44.203,23.488-69.076,23.488c-24.877,
1297 1297 0-48.762-8.122-69.078-23.488
1298 1298 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1299 1299 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1300 1300 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1301 1301 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1302 1302 19.402-10.527 C409.699,390.129,
1303 1303 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1304 1304 </svg>""".format(
1305 1305 size=self.size,
1306 1306 background='#979797', # @grey4
1307 1307 text_color=self.text_color,
1308 1308 font_family=font_family)
1309 1309
1310 1310 return {
1311 1311 "default_user": default_user
1312 1312 }[img_type]
1313 1313
1314 1314 def get_img_data(self, svg_type=None):
1315 1315 """
1316 1316 generates the svg metadata for image
1317 1317 """
1318 1318 fonts = [
1319 1319 '-apple-system',
1320 1320 'BlinkMacSystemFont',
1321 1321 'Segoe UI',
1322 1322 'Roboto',
1323 1323 'Oxygen-Sans',
1324 1324 'Ubuntu',
1325 1325 'Cantarell',
1326 1326 'Helvetica Neue',
1327 1327 'sans-serif'
1328 1328 ]
1329 1329 font_family = ','.join(fonts)
1330 1330 if svg_type:
1331 1331 return self.get_img_data_by_type(font_family, svg_type)
1332 1332
1333 1333 initials = self.get_initials()
1334 1334 img_data = """
1335 1335 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1336 1336 width="{size}" height="{size}"
1337 1337 style="width: 100%; height: 100%; background-color: {background}"
1338 1338 viewBox="0 0 {size} {size}">
1339 1339 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1340 1340 pointer-events="auto" fill="{text_color}"
1341 1341 font-family="{font_family}"
1342 1342 style="font-weight: 400; font-size: {f_size}px;">{text}
1343 1343 </text>
1344 1344 </svg>""".format(
1345 1345 size=self.size,
1346 1346 f_size=self.size/2.05, # scale the text inside the box nicely
1347 1347 background=self.background,
1348 1348 text_color=self.text_color,
1349 1349 text=initials.upper(),
1350 1350 font_family=font_family)
1351 1351
1352 1352 return img_data
1353 1353
1354 1354 def generate_svg(self, svg_type=None):
1355 1355 img_data = self.get_img_data(svg_type)
1356 1356 return "data:image/svg+xml;base64,%s" % base64.b64encode(img_data)
1357 1357
1358 1358
1359 1359 def initials_gravatar(email_address, first_name, last_name, size=30):
1360 1360 svg_type = None
1361 1361 if email_address == User.DEFAULT_USER_EMAIL:
1362 1362 svg_type = 'default_user'
1363 1363 klass = InitialsGravatar(email_address, first_name, last_name, size)
1364 1364 return klass.generate_svg(svg_type=svg_type)
1365 1365
1366 1366
1367 1367 def gravatar_url(email_address, size=30, request=None):
1368 1368 request = get_current_request()
1369 1369 _use_gravatar = request.call_context.visual.use_gravatar
1370 1370 _gravatar_url = request.call_context.visual.gravatar_url
1371 1371
1372 1372 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1373 1373
1374 1374 email_address = email_address or User.DEFAULT_USER_EMAIL
1375 1375 if isinstance(email_address, unicode):
1376 1376 # hashlib crashes on unicode items
1377 1377 email_address = safe_str(email_address)
1378 1378
1379 1379 # empty email or default user
1380 1380 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1381 1381 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1382 1382
1383 1383 if _use_gravatar:
1384 1384 # TODO: Disuse pyramid thread locals. Think about another solution to
1385 1385 # get the host and schema here.
1386 1386 request = get_current_request()
1387 1387 tmpl = safe_str(_gravatar_url)
1388 1388 tmpl = tmpl.replace('{email}', email_address)\
1389 1389 .replace('{md5email}', md5_safe(email_address.lower())) \
1390 1390 .replace('{netloc}', request.host)\
1391 1391 .replace('{scheme}', request.scheme)\
1392 1392 .replace('{size}', safe_str(size))
1393 1393 return tmpl
1394 1394 else:
1395 1395 return initials_gravatar(email_address, '', '', size=size)
1396 1396
1397 1397
1398 1398 def breadcrumb_repo_link(repo):
1399 1399 """
1400 1400 Makes a breadcrumbs path link to repo
1401 1401
1402 1402 ex::
1403 1403 group >> subgroup >> repo
1404 1404
1405 1405 :param repo: a Repository instance
1406 1406 """
1407 1407
1408 1408 path = [
1409 1409 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1410 1410 title='last change:{}'.format(format_date(group.last_commit_change)))
1411 1411 for group in repo.groups_with_parents
1412 1412 ] + [
1413 1413 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1414 1414 title='last change:{}'.format(format_date(repo.last_commit_change)))
1415 1415 ]
1416 1416
1417 1417 return literal(' &raquo; '.join(path))
1418 1418
1419 1419
1420 1420 def breadcrumb_repo_group_link(repo_group):
1421 1421 """
1422 1422 Makes a breadcrumbs path link to repo
1423 1423
1424 1424 ex::
1425 1425 group >> subgroup
1426 1426
1427 1427 :param repo_group: a Repository Group instance
1428 1428 """
1429 1429
1430 1430 path = [
1431 1431 link_to(group.name,
1432 1432 route_path('repo_group_home', repo_group_name=group.group_name),
1433 1433 title='last change:{}'.format(format_date(group.last_commit_change)))
1434 1434 for group in repo_group.parents
1435 1435 ] + [
1436 1436 link_to(repo_group.name,
1437 1437 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1438 1438 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1439 1439 ]
1440 1440
1441 1441 return literal(' &raquo; '.join(path))
1442 1442
1443 1443
1444 1444 def format_byte_size_binary(file_size):
1445 1445 """
1446 1446 Formats file/folder sizes to standard.
1447 1447 """
1448 1448 if file_size is None:
1449 1449 file_size = 0
1450 1450
1451 1451 formatted_size = format_byte_size(file_size, binary=True)
1452 1452 return formatted_size
1453 1453
1454 1454
1455 1455 def urlify_text(text_, safe=True, **href_attrs):
1456 1456 """
1457 1457 Extract urls from text and make html links out of them
1458 1458 """
1459 1459
1460 1460 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1461 1461 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1462 1462
1463 1463 def url_func(match_obj):
1464 1464 url_full = match_obj.groups()[0]
1465 1465 a_options = dict(href_attrs)
1466 1466 a_options['href'] = url_full
1467 1467 a_text = url_full
1468 1468 return HTML.tag("a", a_text, **a_options)
1469 1469
1470 1470 _new_text = url_pat.sub(url_func, text_)
1471 1471
1472 1472 if safe:
1473 1473 return literal(_new_text)
1474 1474 return _new_text
1475 1475
1476 1476
1477 1477 def urlify_commits(text_, repo_name):
1478 1478 """
1479 1479 Extract commit ids from text and make link from them
1480 1480
1481 1481 :param text_:
1482 1482 :param repo_name: repo name to build the URL with
1483 1483 """
1484 1484
1485 1485 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1486 1486
1487 1487 def url_func(match_obj):
1488 1488 commit_id = match_obj.groups()[1]
1489 1489 pref = match_obj.groups()[0]
1490 1490 suf = match_obj.groups()[2]
1491 1491
1492 1492 tmpl = (
1493 1493 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1494 1494 '%(commit_id)s</a>%(suf)s'
1495 1495 )
1496 1496 return tmpl % {
1497 1497 'pref': pref,
1498 1498 'cls': 'revision-link',
1499 1499 'url': route_url(
1500 1500 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1501 1501 'commit_id': commit_id,
1502 1502 'suf': suf,
1503 1503 'hovercard_alt': 'Commit: {}'.format(commit_id),
1504 1504 'hovercard_url': route_url(
1505 1505 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1506 1506 }
1507 1507
1508 1508 new_text = url_pat.sub(url_func, text_)
1509 1509
1510 1510 return new_text
1511 1511
1512 1512
1513 1513 def _process_url_func(match_obj, repo_name, uid, entry,
1514 1514 return_raw_data=False, link_format='html'):
1515 1515 pref = ''
1516 1516 if match_obj.group().startswith(' '):
1517 1517 pref = ' '
1518 1518
1519 1519 issue_id = ''.join(match_obj.groups())
1520 1520
1521 1521 if link_format == 'html':
1522 1522 tmpl = (
1523 1523 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1524 1524 '%(issue-prefix)s%(id-repr)s'
1525 1525 '</a>')
1526 1526 elif link_format == 'html+hovercard':
1527 1527 tmpl = (
1528 1528 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1529 1529 '%(issue-prefix)s%(id-repr)s'
1530 1530 '</a>')
1531 1531 elif link_format in ['rst', 'rst+hovercard']:
1532 1532 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1533 1533 elif link_format in ['markdown', 'markdown+hovercard']:
1534 1534 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1535 1535 else:
1536 1536 raise ValueError('Bad link_format:{}'.format(link_format))
1537 1537
1538 1538 (repo_name_cleaned,
1539 1539 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1540 1540
1541 1541 # variables replacement
1542 1542 named_vars = {
1543 1543 'id': issue_id,
1544 1544 'repo': repo_name,
1545 1545 'repo_name': repo_name_cleaned,
1546 1546 'group_name': parent_group_name,
1547 1547 # set dummy keys so we always have them
1548 1548 'hostname': '',
1549 1549 'netloc': '',
1550 1550 'scheme': ''
1551 1551 }
1552 1552
1553 1553 request = get_current_request()
1554 1554 if request:
1555 1555 # exposes, hostname, netloc, scheme
1556 1556 host_data = get_host_info(request)
1557 1557 named_vars.update(host_data)
1558 1558
1559 1559 # named regex variables
1560 1560 named_vars.update(match_obj.groupdict())
1561 1561 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1562 1562 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1563 1563 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1564 1564
1565 1565 def quote_cleaner(input_str):
1566 1566 """Remove quotes as it's HTML"""
1567 1567 return input_str.replace('"', '')
1568 1568
1569 1569 data = {
1570 1570 'pref': pref,
1571 1571 'cls': quote_cleaner('issue-tracker-link'),
1572 1572 'url': quote_cleaner(_url),
1573 1573 'id-repr': issue_id,
1574 1574 'issue-prefix': entry['pref'],
1575 1575 'serv': entry['url'],
1576 1576 'title': bleach.clean(desc, strip=True),
1577 1577 'hovercard_url': hovercard_url
1578 1578 }
1579 1579
1580 1580 if return_raw_data:
1581 1581 return {
1582 1582 'id': issue_id,
1583 1583 'url': _url
1584 1584 }
1585 1585 return tmpl % data
1586 1586
1587 1587
1588 1588 def get_active_pattern_entries(repo_name):
1589 1589 repo = None
1590 1590 if repo_name:
1591 1591 # Retrieving repo_name to avoid invalid repo_name to explode on
1592 1592 # IssueTrackerSettingsModel but still passing invalid name further down
1593 1593 repo = Repository.get_by_repo_name(repo_name, cache=True)
1594 1594
1595 1595 settings_model = IssueTrackerSettingsModel(repo=repo)
1596 1596 active_entries = settings_model.get_settings(cache=True)
1597 1597 return active_entries
1598 1598
1599 1599
1600 1600 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1601 1601
1602 1602
1603 1603 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1604 1604
1605 1605 allowed_formats = ['html', 'rst', 'markdown',
1606 1606 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1607 1607 if link_format not in allowed_formats:
1608 1608 raise ValueError('Link format can be only one of:{} got {}'.format(
1609 1609 allowed_formats, link_format))
1610 1610
1611 1611 if active_entries is None:
1612 1612 log.debug('Fetch active patterns for repo: %s', repo_name)
1613 1613 active_entries = get_active_pattern_entries(repo_name)
1614 1614
1615 1615 issues_data = []
1616 1616 new_text = text_string
1617 1617
1618 1618 log.debug('Got %s entries to process', len(active_entries))
1619 1619 for uid, entry in active_entries.items():
1620 1620 log.debug('found issue tracker entry with uid %s', uid)
1621 1621
1622 1622 if not (entry['pat'] and entry['url']):
1623 1623 log.debug('skipping due to missing data')
1624 1624 continue
1625 1625
1626 1626 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1627 1627 uid, entry['pat'], entry['url'], entry['pref'])
1628 1628
1629 1629 if entry.get('pat_compiled'):
1630 1630 pattern = entry['pat_compiled']
1631 1631 else:
1632 1632 try:
1633 1633 pattern = re.compile(r'%s' % entry['pat'])
1634 1634 except re.error:
1635 1635 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1636 1636 continue
1637 1637
1638 1638 data_func = partial(
1639 1639 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1640 1640 return_raw_data=True)
1641 1641
1642 1642 for match_obj in pattern.finditer(text_string):
1643 1643 issues_data.append(data_func(match_obj))
1644 1644
1645 1645 url_func = partial(
1646 1646 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1647 1647 link_format=link_format)
1648 1648
1649 1649 new_text = pattern.sub(url_func, new_text)
1650 1650 log.debug('processed prefix:uid `%s`', uid)
1651 1651
1652 1652 # finally use global replace, eg !123 -> pr-link, those will not catch
1653 1653 # if already similar pattern exists
1654 1654 server_url = '${scheme}://${netloc}'
1655 1655 pr_entry = {
1656 1656 'pref': '!',
1657 1657 'url': server_url + '/_admin/pull-requests/${id}',
1658 1658 'desc': 'Pull Request !${id}',
1659 1659 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1660 1660 }
1661 1661 pr_url_func = partial(
1662 1662 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1663 1663 link_format=link_format+'+hovercard')
1664 1664 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1665 1665 log.debug('processed !pr pattern')
1666 1666
1667 1667 return new_text, issues_data
1668 1668
1669 1669
1670 1670 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1671 1671 """
1672 1672 Parses given text message and makes proper links.
1673 1673 issues are linked to given issue-server, and rest is a commit link
1674 1674 """
1675 1675
1676 1676 def escaper(_text):
1677 1677 return _text.replace('<', '&lt;').replace('>', '&gt;')
1678 1678
1679 1679 new_text = escaper(commit_text)
1680 1680
1681 1681 # extract http/https links and make them real urls
1682 1682 new_text = urlify_text(new_text, safe=False)
1683 1683
1684 1684 # urlify commits - extract commit ids and make link out of them, if we have
1685 1685 # the scope of repository present.
1686 1686 if repository:
1687 1687 new_text = urlify_commits(new_text, repository)
1688 1688
1689 1689 # process issue tracker patterns
1690 1690 new_text, issues = process_patterns(new_text, repository or '',
1691 1691 active_entries=active_pattern_entries)
1692 1692
1693 1693 return literal(new_text)
1694 1694
1695 1695
1696 1696 def render_binary(repo_name, file_obj):
1697 1697 """
1698 1698 Choose how to render a binary file
1699 1699 """
1700 1700
1701 1701 # unicode
1702 1702 filename = file_obj.name
1703 1703
1704 1704 # images
1705 1705 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1706 1706 if fnmatch.fnmatch(filename, pat=ext):
1707 1707 src = route_path(
1708 1708 'repo_file_raw', repo_name=repo_name,
1709 1709 commit_id=file_obj.commit.raw_id,
1710 1710 f_path=file_obj.path)
1711 1711
1712 1712 return literal(
1713 1713 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1714 1714
1715 1715
1716 1716 def renderer_from_filename(filename, exclude=None):
1717 1717 """
1718 1718 choose a renderer based on filename, this works only for text based files
1719 1719 """
1720 1720
1721 1721 # ipython
1722 1722 for ext in ['*.ipynb']:
1723 1723 if fnmatch.fnmatch(filename, pat=ext):
1724 1724 return 'jupyter'
1725 1725
1726 1726 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1727 1727 if is_markup:
1728 1728 return is_markup
1729 1729 return None
1730 1730
1731 1731
1732 1732 def render(source, renderer='rst', mentions=False, relative_urls=None,
1733 1733 repo_name=None, active_pattern_entries=None):
1734 1734
1735 1735 def maybe_convert_relative_links(html_source):
1736 1736 if relative_urls:
1737 1737 return relative_links(html_source, relative_urls)
1738 1738 return html_source
1739 1739
1740 1740 if renderer == 'plain':
1741 1741 return literal(
1742 1742 MarkupRenderer.plain(source, leading_newline=False))
1743 1743
1744 1744 elif renderer == 'rst':
1745 1745 if repo_name:
1746 1746 # process patterns on comments if we pass in repo name
1747 1747 source, issues = process_patterns(
1748 1748 source, repo_name, link_format='rst',
1749 1749 active_entries=active_pattern_entries)
1750 1750
1751 1751 return literal(
1752 1752 '<div class="rst-block">%s</div>' %
1753 1753 maybe_convert_relative_links(
1754 1754 MarkupRenderer.rst(source, mentions=mentions)))
1755 1755
1756 1756 elif renderer == 'markdown':
1757 1757 if repo_name:
1758 1758 # process patterns on comments if we pass in repo name
1759 1759 source, issues = process_patterns(
1760 1760 source, repo_name, link_format='markdown',
1761 1761 active_entries=active_pattern_entries)
1762 1762
1763 1763 return literal(
1764 1764 '<div class="markdown-block">%s</div>' %
1765 1765 maybe_convert_relative_links(
1766 1766 MarkupRenderer.markdown(source, flavored=True,
1767 1767 mentions=mentions)))
1768 1768
1769 1769 elif renderer == 'jupyter':
1770 1770 return literal(
1771 1771 '<div class="ipynb">%s</div>' %
1772 1772 maybe_convert_relative_links(
1773 1773 MarkupRenderer.jupyter(source)))
1774 1774
1775 1775 # None means just show the file-source
1776 1776 return None
1777 1777
1778 1778
1779 1779 def commit_status(repo, commit_id):
1780 1780 return ChangesetStatusModel().get_status(repo, commit_id)
1781 1781
1782 1782
1783 1783 def commit_status_lbl(commit_status):
1784 1784 return dict(ChangesetStatus.STATUSES).get(commit_status)
1785 1785
1786 1786
1787 1787 def commit_time(repo_name, commit_id):
1788 1788 repo = Repository.get_by_repo_name(repo_name)
1789 1789 commit = repo.get_commit(commit_id=commit_id)
1790 1790 return commit.date
1791 1791
1792 1792
1793 1793 def get_permission_name(key):
1794 1794 return dict(Permission.PERMS).get(key)
1795 1795
1796 1796
1797 1797 def journal_filter_help(request):
1798 1798 _ = request.translate
1799 1799 from rhodecode.lib.audit_logger import ACTIONS
1800 1800 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1801 1801
1802 1802 return _(
1803 1803 'Example filter terms:\n' +
1804 1804 ' repository:vcs\n' +
1805 1805 ' username:marcin\n' +
1806 1806 ' username:(NOT marcin)\n' +
1807 1807 ' action:*push*\n' +
1808 1808 ' ip:127.0.0.1\n' +
1809 1809 ' date:20120101\n' +
1810 1810 ' date:[20120101100000 TO 20120102]\n' +
1811 1811 '\n' +
1812 1812 'Actions: {actions}\n' +
1813 1813 '\n' +
1814 1814 'Generate wildcards using \'*\' character:\n' +
1815 1815 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1816 1816 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1817 1817 '\n' +
1818 1818 'Optional AND / OR operators in queries\n' +
1819 1819 ' "repository:vcs OR repository:test"\n' +
1820 1820 ' "username:test AND repository:test*"\n'
1821 1821 ).format(actions=actions)
1822 1822
1823 1823
1824 1824 def not_mapped_error(repo_name):
1825 1825 from rhodecode.translation import _
1826 1826 flash(_('%s repository is not mapped to db perhaps'
1827 1827 ' it was created or renamed from the filesystem'
1828 1828 ' please run the application again'
1829 1829 ' in order to rescan repositories') % repo_name, category='error')
1830 1830
1831 1831
1832 1832 def ip_range(ip_addr):
1833 1833 from rhodecode.model.db import UserIpMap
1834 1834 s, e = UserIpMap._get_ip_range(ip_addr)
1835 1835 return '%s - %s' % (s, e)
1836 1836
1837 1837
1838 1838 def form(url, method='post', needs_csrf_token=True, **attrs):
1839 1839 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1840 1840 if method.lower() != 'get' and needs_csrf_token:
1841 1841 raise Exception(
1842 1842 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1843 1843 'CSRF token. If the endpoint does not require such token you can ' +
1844 1844 'explicitly set the parameter needs_csrf_token to false.')
1845 1845
1846 1846 return insecure_form(url, method=method, **attrs)
1847 1847
1848 1848
1849 1849 def secure_form(form_url, method="POST", multipart=False, **attrs):
1850 1850 """Start a form tag that points the action to an url. This
1851 1851 form tag will also include the hidden field containing
1852 1852 the auth token.
1853 1853
1854 1854 The url options should be given either as a string, or as a
1855 1855 ``url()`` function. The method for the form defaults to POST.
1856 1856
1857 1857 Options:
1858 1858
1859 1859 ``multipart``
1860 1860 If set to True, the enctype is set to "multipart/form-data".
1861 1861 ``method``
1862 1862 The method to use when submitting the form, usually either
1863 1863 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1864 1864 hidden input with name _method is added to simulate the verb
1865 1865 over POST.
1866 1866
1867 1867 """
1868 1868
1869 1869 if 'request' in attrs:
1870 1870 session = attrs['request'].session
1871 1871 del attrs['request']
1872 1872 else:
1873 1873 raise ValueError(
1874 1874 'Calling this form requires request= to be passed as argument')
1875 1875
1876 1876 _form = insecure_form(form_url, method, multipart, **attrs)
1877 1877 token = literal(
1878 1878 '<input type="hidden" name="{}" value="{}">'.format(
1879 1879 csrf_token_key, get_csrf_token(session)))
1880 1880
1881 1881 return literal("%s\n%s" % (_form, token))
1882 1882
1883 1883
1884 1884 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1885 1885 select_html = select(name, selected, options, **attrs)
1886 1886
1887 1887 select2 = """
1888 1888 <script>
1889 1889 $(document).ready(function() {
1890 1890 $('#%s').select2({
1891 1891 containerCssClass: 'drop-menu %s',
1892 1892 dropdownCssClass: 'drop-menu-dropdown',
1893 1893 dropdownAutoWidth: true%s
1894 1894 });
1895 1895 });
1896 1896 </script>
1897 1897 """
1898 1898
1899 1899 filter_option = """,
1900 1900 minimumResultsForSearch: -1
1901 1901 """
1902 1902 input_id = attrs.get('id') or name
1903 1903 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1904 1904 filter_enabled = "" if enable_filter else filter_option
1905 1905 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1906 1906
1907 1907 return literal(select_html+select_script)
1908 1908
1909 1909
1910 1910 def get_visual_attr(tmpl_context_var, attr_name):
1911 1911 """
1912 1912 A safe way to get a variable from visual variable of template context
1913 1913
1914 1914 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1915 1915 :param attr_name: name of the attribute we fetch from the c.visual
1916 1916 """
1917 1917 visual = getattr(tmpl_context_var, 'visual', None)
1918 1918 if not visual:
1919 1919 return
1920 1920 else:
1921 1921 return getattr(visual, attr_name, None)
1922 1922
1923 1923
1924 1924 def get_last_path_part(file_node):
1925 1925 if not file_node.path:
1926 1926 return u'/'
1927 1927
1928 1928 path = safe_unicode(file_node.path.split('/')[-1])
1929 1929 return u'../' + path
1930 1930
1931 1931
1932 1932 def route_url(*args, **kwargs):
1933 1933 """
1934 1934 Wrapper around pyramids `route_url` (fully qualified url) function.
1935 1935 """
1936 1936 req = get_current_request()
1937 1937 return req.route_url(*args, **kwargs)
1938 1938
1939 1939
1940 1940 def route_path(*args, **kwargs):
1941 1941 """
1942 1942 Wrapper around pyramids `route_path` function.
1943 1943 """
1944 1944 req = get_current_request()
1945 1945 return req.route_path(*args, **kwargs)
1946 1946
1947 1947
1948 1948 def route_path_or_none(*args, **kwargs):
1949 1949 try:
1950 1950 return route_path(*args, **kwargs)
1951 1951 except KeyError:
1952 1952 return None
1953 1953
1954 1954
1955 1955 def current_route_path(request, **kw):
1956 1956 new_args = request.GET.mixed()
1957 1957 new_args.update(kw)
1958 1958 return request.current_route_path(_query=new_args)
1959 1959
1960 1960
1961 1961 def curl_api_example(method, args):
1962 1962 args_json = json.dumps(OrderedDict([
1963 1963 ('id', 1),
1964 1964 ('auth_token', 'SECRET'),
1965 1965 ('method', method),
1966 1966 ('args', args)
1967 1967 ]))
1968 1968
1969 1969 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1970 1970 api_url=route_url('apiv2'),
1971 1971 args_json=args_json
1972 1972 )
1973 1973
1974 1974
1975 1975 def api_call_example(method, args):
1976 1976 """
1977 1977 Generates an API call example via CURL
1978 1978 """
1979 1979 curl_call = curl_api_example(method, args)
1980 1980
1981 1981 return literal(
1982 1982 curl_call +
1983 1983 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1984 1984 "and needs to be of `api calls` role."
1985 1985 .format(token_url=route_url('my_account_auth_tokens')))
1986 1986
1987 1987
1988 1988 def notification_description(notification, request):
1989 1989 """
1990 1990 Generate notification human readable description based on notification type
1991 1991 """
1992 1992 from rhodecode.model.notification import NotificationModel
1993 1993 return NotificationModel().make_description(
1994 1994 notification, translate=request.translate)
1995 1995
1996 1996
1997 1997 def go_import_header(request, db_repo=None):
1998 1998 """
1999 1999 Creates a header for go-import functionality in Go Lang
2000 2000 """
2001 2001
2002 2002 if not db_repo:
2003 2003 return
2004 2004 if 'go-get' not in request.GET:
2005 2005 return
2006 2006
2007 2007 clone_url = db_repo.clone_url()
2008 2008 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2009 2009 # we have a repo and go-get flag,
2010 2010 return literal('<meta name="go-import" content="{} {} {}">'.format(
2011 2011 prefix, db_repo.repo_type, clone_url))
2012 2012
2013 2013
2014 2014 def reviewer_as_json(*args, **kwargs):
2015 2015 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2016 2016 return _reviewer_as_json(*args, **kwargs)
2017 2017
2018 2018
2019 2019 def get_repo_view_type(request):
2020 2020 route_name = request.matched_route.name
2021 2021 route_to_view_type = {
2022 2022 'repo_changelog': 'commits',
2023 2023 'repo_commits': 'commits',
2024 2024 'repo_files': 'files',
2025 2025 'repo_summary': 'summary',
2026 2026 'repo_commit': 'commit'
2027 2027 }
2028 2028
2029 2029 return route_to_view_type.get(route_name)
2030 2030
2031 2031
2032 2032 def is_active(menu_entry, selected):
2033 2033 """
2034 2034 Returns active class for selecting menus in templates
2035 2035 <li class=${h.is_active('settings', current_active)}></li>
2036 2036 """
2037 2037 if not isinstance(menu_entry, list):
2038 2038 menu_entry = [menu_entry]
2039 2039
2040 2040 if selected in menu_entry:
2041 2041 return "active"
@@ -1,1206 +1,1208 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%!
4 4 from rhodecode.lib import html_filters
5 5 %>
6 6
7 7 <%inherit file="root.mako"/>
8 8
9 9 <%include file="/ejs_templates/templates.html"/>
10 10
11 11 <div class="outerwrapper">
12 12 <!-- HEADER -->
13 13 <div class="header">
14 14 <div id="header-inner" class="wrapper">
15 15 <div id="logo">
16 16 <div class="logo-wrapper">
17 17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
18 18 </div>
19 19 % if c.rhodecode_name:
20 20 <div class="branding">
21 21 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
22 22 </div>
23 23 % endif
24 24 </div>
25 25 <!-- MENU BAR NAV -->
26 26 ${self.menu_bar_nav()}
27 27 <!-- END MENU BAR NAV -->
28 28 </div>
29 29 </div>
30 30 ${self.menu_bar_subnav()}
31 31 <!-- END HEADER -->
32 32
33 33 <!-- CONTENT -->
34 34 <div id="content" class="wrapper">
35 35
36 36 <rhodecode-toast id="notifications"></rhodecode-toast>
37 37
38 38 <div class="main">
39 39 ${next.main()}
40 40 </div>
41 41 </div>
42 42 <!-- END CONTENT -->
43 43
44 44 </div>
45 45 <!-- FOOTER -->
46 46 <div id="footer">
47 47 <div id="footer-inner" class="title wrapper">
48 48 <div>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50
51 51 <p class="footer-link-right">
52 52 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
53 53 RhodeCode
54 54 % if c.visual.show_version:
55 55 ${c.rhodecode_version}
56 56 % endif
57 57 ${c.rhodecode_edition}
58 58 </a> |
59 59
60 60 % if c.visual.rhodecode_support_url:
61 61 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
62 62 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
63 63 % endif
64 64
65 65 </p>
66 66
67 67 <p class="server-instance" style="display:${sid}">
68 68 ## display hidden instance ID if specially defined
69 69 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
70 70 % if c.rhodecode_instanceid:
71 71 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
72 72 % endif
73 73 </p>
74 74 </div>
75 75 </div>
76 76 </div>
77 77
78 78 <!-- END FOOTER -->
79 79
80 80 ### MAKO DEFS ###
81 81
82 82 <%def name="menu_bar_subnav()">
83 83 </%def>
84 84
85 85 <%def name="breadcrumbs(class_='breadcrumbs')">
86 86 <div class="${class_}">
87 87 ${self.breadcrumbs_links()}
88 88 </div>
89 89 </%def>
90 90
91 91 <%def name="admin_menu(active=None)">
92 92
93 93 <div id="context-bar">
94 94 <div class="wrapper">
95 95 <div class="title">
96 96 <div class="title-content">
97 97 <div class="title-main">
98 98 % if c.is_super_admin:
99 99 ${_('Super-admin Panel')}
100 100 % else:
101 101 ${_('Delegated Admin Panel')}
102 102 % endif
103 103 </div>
104 104 </div>
105 105 </div>
106 106
107 107 <ul id="context-pages" class="navigation horizontal-list">
108 108
109 109 ## super-admin case
110 110 % if c.is_super_admin:
111 111 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
112 112 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
113 113 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
114 114 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
115 115 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
116 116 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
117 117 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
118 118 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
119 119 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
120 120 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
121 121
122 122 ## delegated admin
123 123 % elif c.is_delegated_admin:
124 124 <%
125 125 repositories=c.auth_user.repositories_admin or c.can_create_repo
126 126 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
127 127 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
128 128 %>
129 129
130 130 %if repositories:
131 131 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
132 132 %endif
133 133 %if repository_groups:
134 134 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
135 135 %endif
136 136 %if user_groups:
137 137 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
138 138 %endif
139 139 % endif
140 140 </ul>
141 141
142 142 </div>
143 143 <div class="clear"></div>
144 144 </div>
145 145 </%def>
146 146
147 147 <%def name="dt_info_panel(elements)">
148 148 <dl class="dl-horizontal">
149 149 %for dt, dd, title, show_items in elements:
150 150 <dt>${dt}:</dt>
151 151 <dd title="${h.tooltip(title)}">
152 152 %if callable(dd):
153 153 ## allow lazy evaluation of elements
154 154 ${dd()}
155 155 %else:
156 156 ${dd}
157 157 %endif
158 158 %if show_items:
159 159 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
160 160 %endif
161 161 </dd>
162 162
163 163 %if show_items:
164 164 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
165 165 %for item in show_items:
166 166 <dt></dt>
167 167 <dd>${item}</dd>
168 168 %endfor
169 169 </div>
170 170 %endif
171 171
172 172 %endfor
173 173 </dl>
174 174 </%def>
175 175
176 176 <%def name="tr_info_entry(element)">
177 177 <% key, val, title, show_items = element %>
178 178
179 179 <tr>
180 180 <td style="vertical-align: top">${key}</td>
181 181 <td title="${h.tooltip(title)}">
182 182 %if callable(val):
183 183 ## allow lazy evaluation of elements
184 184 ${val()}
185 185 %else:
186 186 ${val}
187 187 %endif
188 188 %if show_items:
189 189 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
190 190 % for item in show_items:
191 191 <dt></dt>
192 192 <dd>${item}</dd>
193 193 % endfor
194 194 </div>
195 195 %endif
196 196 </td>
197 197 <td style="vertical-align: top">
198 198 %if show_items:
199 199 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
200 200 %endif
201 201 </td>
202 202 </tr>
203 203
204 204 </%def>
205 205
206 206 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
207 207 <%
208 208 if size > 16:
209 209 gravatar_class = ['gravatar','gravatar-large']
210 210 else:
211 211 gravatar_class = ['gravatar']
212 212
213 213 data_hovercard_url = ''
214 214 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
215 215
216 216 if tooltip:
217 217 gravatar_class += ['tooltip-hovercard']
218 218 if extra_class:
219 219 gravatar_class += extra_class
220 220 if tooltip and user:
221 221 if user.username == h.DEFAULT_USER:
222 222 gravatar_class.pop(-1)
223 223 else:
224 224 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
225 225 gravatar_class = ' '.join(gravatar_class)
226 226
227 227 %>
228 228 <%doc>
229 229 TODO: johbo: For now we serve double size images to make it smooth
230 230 for retina. This is how it worked until now. Should be replaced
231 231 with a better solution at some point.
232 232 </%doc>
233 233
234 234 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
235 235 </%def>
236 236
237 237
238 238 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
239 239 <%
240 240 email = h.email_or_none(contact)
241 241 rc_user = h.discover_user(contact)
242 242 %>
243 243
244 244 <div class="${_class}">
245 245 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
246 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
246 <span class="${('user user-disabled' if show_disabled else 'user')}">
247 ${h.link_to_user(rc_user or contact)}
248 </span>
247 249 </div>
248 250 </%def>
249 251
250 252
251 253 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
252 254 <%
253 255 if (size > 16):
254 256 gravatar_class = 'icon-user-group-alt'
255 257 else:
256 258 gravatar_class = 'icon-user-group-alt'
257 259
258 260 if tooltip:
259 261 gravatar_class += ' tooltip-hovercard'
260 262
261 263 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
262 264 %>
263 265 <%doc>
264 266 TODO: johbo: For now we serve double size images to make it smooth
265 267 for retina. This is how it worked until now. Should be replaced
266 268 with a better solution at some point.
267 269 </%doc>
268 270
269 271 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
270 272 </%def>
271 273
272 274 <%def name="repo_page_title(repo_instance)">
273 275 <div class="title-content repo-title">
274 276
275 277 <div class="title-main">
276 278 ## SVN/HG/GIT icons
277 279 %if h.is_hg(repo_instance):
278 280 <i class="icon-hg"></i>
279 281 %endif
280 282 %if h.is_git(repo_instance):
281 283 <i class="icon-git"></i>
282 284 %endif
283 285 %if h.is_svn(repo_instance):
284 286 <i class="icon-svn"></i>
285 287 %endif
286 288
287 289 ## public/private
288 290 %if repo_instance.private:
289 291 <i class="icon-repo-private"></i>
290 292 %else:
291 293 <i class="icon-repo-public"></i>
292 294 %endif
293 295
294 296 ## repo name with group name
295 297 ${h.breadcrumb_repo_link(repo_instance)}
296 298
297 299 ## Context Actions
298 300 <div class="pull-right">
299 301 %if c.rhodecode_user.username != h.DEFAULT_USER:
300 302 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
301 303
302 304 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
303 305 % if c.repository_is_user_following:
304 306 <i class="icon-eye-off"></i>${_('Unwatch')}
305 307 % else:
306 308 <i class="icon-eye"></i>${_('Watch')}
307 309 % endif
308 310
309 311 </a>
310 312 %else:
311 313 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
312 314 %endif
313 315 </div>
314 316
315 317 </div>
316 318
317 319 ## FORKED
318 320 %if repo_instance.fork:
319 321 <p class="discreet">
320 322 <i class="icon-code-fork"></i> ${_('Fork of')}
321 323 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
322 324 </p>
323 325 %endif
324 326
325 327 ## IMPORTED FROM REMOTE
326 328 %if repo_instance.clone_uri:
327 329 <p class="discreet">
328 330 <i class="icon-code-fork"></i> ${_('Clone from')}
329 331 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
330 332 </p>
331 333 %endif
332 334
333 335 ## LOCKING STATUS
334 336 %if repo_instance.locked[0]:
335 337 <p class="locking_locked discreet">
336 338 <i class="icon-repo-lock"></i>
337 339 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
338 340 </p>
339 341 %elif repo_instance.enable_locking:
340 342 <p class="locking_unlocked discreet">
341 343 <i class="icon-repo-unlock"></i>
342 344 ${_('Repository not locked. Pull repository to lock it.')}
343 345 </p>
344 346 %endif
345 347
346 348 </div>
347 349 </%def>
348 350
349 351 <%def name="repo_menu(active=None)">
350 352 <%
351 353 ## determine if we have "any" option available
352 354 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
353 355 has_actions = can_lock
354 356
355 357 %>
356 358 % if c.rhodecode_db_repo.archived:
357 359 <div class="alert alert-warning text-center">
358 360 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
359 361 </div>
360 362 % endif
361 363
362 364 <!--- REPO CONTEXT BAR -->
363 365 <div id="context-bar">
364 366 <div class="wrapper">
365 367
366 368 <div class="title">
367 369 ${self.repo_page_title(c.rhodecode_db_repo)}
368 370 </div>
369 371
370 372 <ul id="context-pages" class="navigation horizontal-list">
371 373 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
372 374 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
373 375 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
374 376 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
375 377
376 378 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
377 379 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
378 380 <li class="${h.is_active('showpullrequest', active)}">
379 381 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
380 382 <div class="menulabel">
381 383 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
382 384 </div>
383 385 </a>
384 386 </li>
385 387 %endif
386 388
387 389 <li class="${h.is_active('artifacts', active)}">
388 390 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
389 391 <div class="menulabel">
390 392 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
391 393 </div>
392 394 </a>
393 395 </li>
394 396
395 397 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
396 398 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
397 399 %endif
398 400
399 401 <li class="${h.is_active('options', active)}">
400 402 % if has_actions:
401 403 <a class="menulink dropdown">
402 404 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
403 405 </a>
404 406 <ul class="submenu">
405 407 %if can_lock:
406 408 %if c.rhodecode_db_repo.locked[0]:
407 409 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
408 410 %else:
409 411 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
410 412 %endif
411 413 %endif
412 414 </ul>
413 415 % endif
414 416 </li>
415 417
416 418 </ul>
417 419 </div>
418 420 <div class="clear"></div>
419 421 </div>
420 422
421 423 <!--- REPO END CONTEXT BAR -->
422 424
423 425 </%def>
424 426
425 427 <%def name="repo_group_page_title(repo_group_instance)">
426 428 <div class="title-content">
427 429 <div class="title-main">
428 430 ## Repository Group icon
429 431 <i class="icon-repo-group"></i>
430 432
431 433 ## repo name with group name
432 434 ${h.breadcrumb_repo_group_link(repo_group_instance)}
433 435 </div>
434 436
435 437 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
436 438 <div class="repo-group-desc discreet">
437 439 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
438 440 </div>
439 441
440 442 </div>
441 443 </%def>
442 444
443 445
444 446 <%def name="repo_group_menu(active=None)">
445 447 <%
446 448 gr_name = c.repo_group.group_name if c.repo_group else None
447 449 # create repositories with write permission on group is set to true
448 450 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
449 451
450 452 %>
451 453
452 454
453 455 <!--- REPO GROUP CONTEXT BAR -->
454 456 <div id="context-bar">
455 457 <div class="wrapper">
456 458 <div class="title">
457 459 ${self.repo_group_page_title(c.repo_group)}
458 460 </div>
459 461
460 462 <ul id="context-pages" class="navigation horizontal-list">
461 463 <li class="${h.is_active('home', active)}">
462 464 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
463 465 </li>
464 466 % if c.is_super_admin or group_admin:
465 467 <li class="${h.is_active('settings', active)}">
466 468 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
467 469 </li>
468 470 % endif
469 471
470 472 </ul>
471 473 </div>
472 474 <div class="clear"></div>
473 475 </div>
474 476
475 477 <!--- REPO GROUP CONTEXT BAR -->
476 478
477 479 </%def>
478 480
479 481
480 482 <%def name="usermenu(active=False)">
481 483 <%
482 484 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
483 485
484 486 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
485 487 # create repositories with write permission on group is set to true
486 488
487 489 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
488 490 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
489 491 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
490 492 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
491 493
492 494 can_create_repos = c.is_super_admin or c.can_create_repo
493 495 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
494 496
495 497 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
496 498 can_create_repo_groups_in_group = c.is_super_admin or group_admin
497 499 %>
498 500
499 501 % if not_anonymous:
500 502 <%
501 503 default_target_group = dict()
502 504 if c.rhodecode_user.personal_repo_group:
503 505 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
504 506 %>
505 507
506 508 ## create action
507 509 <li>
508 510 <a href="#create-actions" onclick="return false;" class="menulink childs">
509 511 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
510 512 </a>
511 513
512 514 <div class="action-menu submenu">
513 515
514 516 <ol>
515 517 ## scope of within a repository
516 518 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
517 519 <li class="submenu-title">${_('This Repository')}</li>
518 520 <li>
519 521 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
520 522 </li>
521 523 % if can_fork:
522 524 <li>
523 525 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
524 526 </li>
525 527 % endif
526 528 % endif
527 529
528 530 ## scope of within repository groups
529 531 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
530 532 <li class="submenu-title">${_('This Repository Group')}</li>
531 533
532 534 % if can_create_repos_in_group:
533 535 <li>
534 536 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
535 537 </li>
536 538 % endif
537 539
538 540 % if can_create_repo_groups_in_group:
539 541 <li>
540 542 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
541 543 </li>
542 544 % endif
543 545 % endif
544 546
545 547 ## personal group
546 548 % if c.rhodecode_user.personal_repo_group:
547 549 <li class="submenu-title">Personal Group</li>
548 550
549 551 <li>
550 552 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
551 553 </li>
552 554
553 555 <li>
554 556 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
555 557 </li>
556 558 % endif
557 559
558 560 ## Global actions
559 561 <li class="submenu-title">RhodeCode</li>
560 562 % if can_create_repos:
561 563 <li>
562 564 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
563 565 </li>
564 566 % endif
565 567
566 568 % if can_create_repo_groups:
567 569 <li>
568 570 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
569 571 </li>
570 572 % endif
571 573
572 574 <li>
573 575 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
574 576 </li>
575 577
576 578 </ol>
577 579
578 580 </div>
579 581 </li>
580 582
581 583 ## notifications
582 584 <li>
583 585 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
584 586 ${c.unread_notifications}
585 587 </a>
586 588 </li>
587 589 % endif
588 590
589 591 ## USER MENU
590 592 <li id="quick_login_li" class="${'active' if active else ''}">
591 593 % if c.rhodecode_user.username == h.DEFAULT_USER:
592 594 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
593 595 ${gravatar(c.rhodecode_user.email, 20)}
594 596 <span class="user">
595 597 <span>${_('Sign in')}</span>
596 598 </span>
597 599 </a>
598 600 % else:
599 601 ## logged in user
600 602 <a id="quick_login_link" class="menulink childs">
601 603 ${gravatar(c.rhodecode_user.email, 20)}
602 604 <span class="user">
603 605 <span class="menu_link_user">${c.rhodecode_user.username}</span>
604 606 <div class="show_more"></div>
605 607 </span>
606 608 </a>
607 609 ## subnav with menu for logged in user
608 610 <div class="user-menu submenu">
609 611 <div id="quick_login">
610 612 %if c.rhodecode_user.username != h.DEFAULT_USER:
611 613 <div class="">
612 614 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
613 615 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
614 616 <div class="email">${c.rhodecode_user.email}</div>
615 617 </div>
616 618 <div class="">
617 619 <ol class="links">
618 620 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
619 621 % if c.rhodecode_user.personal_repo_group:
620 622 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
621 623 % endif
622 624 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
623 625
624 626 % if c.debug_style:
625 627 <li>
626 628 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
627 629 <div class="menulabel">${_('[Style]')}</div>
628 630 </a>
629 631 </li>
630 632 % endif
631 633
632 634 ## bookmark-items
633 635 <li class="bookmark-items">
634 636 ${_('Bookmarks')}
635 637 <div class="pull-right">
636 638 <a href="${h.route_path('my_account_bookmarks')}">
637 639
638 640 <i class="icon-cog"></i>
639 641 </a>
640 642 </div>
641 643 </li>
642 644 % if not c.bookmark_items:
643 645 <li>
644 646 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
645 647 </li>
646 648 % endif
647 649 % for item in c.bookmark_items:
648 650 <li>
649 651 % if item.repository:
650 652 <div>
651 653 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
652 654 <code>${item.position}</code>
653 655 % if item.repository.repo_type == 'hg':
654 656 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
655 657 % elif item.repository.repo_type == 'git':
656 658 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
657 659 % elif item.repository.repo_type == 'svn':
658 660 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
659 661 % endif
660 662 ${(item.title or h.shorter(item.repository.repo_name, 30))}
661 663 </a>
662 664 </div>
663 665 % elif item.repository_group:
664 666 <div>
665 667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
666 668 <code>${item.position}</code>
667 669 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
668 670 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
669 671 </a>
670 672 </div>
671 673 % else:
672 674 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
673 675 <code>${item.position}</code>
674 676 ${item.title}
675 677 </a>
676 678 % endif
677 679 </li>
678 680 % endfor
679 681
680 682 <li class="logout">
681 683 ${h.secure_form(h.route_path('logout'), request=request)}
682 684 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
683 685 ${h.end_form()}
684 686 </li>
685 687 </ol>
686 688 </div>
687 689 %endif
688 690 </div>
689 691 </div>
690 692
691 693 % endif
692 694 </li>
693 695 </%def>
694 696
695 697 <%def name="menu_items(active=None)">
696 698 <%
697 699 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
698 700 notice_display = 'none' if len(notice_messages) == 0 else ''
699 701 %>
700 702 <style>
701 703
702 704 </style>
703 705
704 706 <ul id="quick" class="main_nav navigation horizontal-list">
705 707 ## notice box for important system messages
706 708 <li style="display: ${notice_display}">
707 709 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
708 710 <div class="menulabel-notice ${notice_level}" >
709 711 ${len(notice_messages)}
710 712 </div>
711 713 </a>
712 714 </li>
713 715 <div class="notice-messages-container" style="display: none">
714 716 <div class="notice-messages">
715 717 <table class="rctable">
716 718 % for notice in notice_messages:
717 719 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
718 720 <td style="vertical-align: text-top; width: 20px">
719 721 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
720 722 </td>
721 723 <td>
722 724 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
723 725 ${notice['subject']}
724 726
725 727 <div id="notice-${notice['msg_id']}" style="display: none">
726 728 ${h.render(notice['body'], renderer='markdown')}
727 729 </div>
728 730 </td>
729 731 <td style="vertical-align: text-top; width: 35px;">
730 732 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
731 733 <i class="icon-remove icon-filled-red"></i>
732 734 </a>
733 735 </td>
734 736 </tr>
735 737
736 738 % endfor
737 739 </table>
738 740 </div>
739 741 </div>
740 742 ## Main filter
741 743 <li>
742 744 <div class="menulabel main_filter_box">
743 745 <div class="main_filter_input_box">
744 746 <ul class="searchItems">
745 747
746 748 <li class="searchTag searchTagIcon">
747 749 <i class="icon-search"></i>
748 750 </li>
749 751
750 752 % if c.template_context['search_context']['repo_id']:
751 753 <li class="searchTag searchTagFilter searchTagHidable" >
752 754 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
753 755 <span class="tag">
754 756 This repo
755 757 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
756 758 </span>
757 759 ##</a>
758 760 </li>
759 761 % elif c.template_context['search_context']['repo_group_id']:
760 762 <li class="searchTag searchTagFilter searchTagHidable">
761 763 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
762 764 <span class="tag">
763 765 This group
764 766 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
765 767 </span>
766 768 ##</a>
767 769 </li>
768 770 % endif
769 771
770 772 <li class="searchTagInput">
771 773 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
772 774 </li>
773 775 <li class="searchTag searchTagHelp">
774 776 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
775 777 </li>
776 778 </ul>
777 779 </div>
778 780 </div>
779 781
780 782 <div id="main_filter_help" style="display: none">
781 783 - Use '/' key to quickly access this field.
782 784
783 785 - Enter a name of repository, or repository group for quick search.
784 786
785 787 - Prefix query to allow special search:
786 788
787 789 user:admin, to search for usernames, always global
788 790
789 791 user_group:devops, to search for user groups, always global
790 792
791 793 pr:303, to search for pull request number, title, or description, always global
792 794
793 795 commit:efced4, to search for commits, scoped to repositories or groups
794 796
795 797 file:models.py, to search for file paths, scoped to repositories or groups
796 798
797 799 % if c.template_context['search_context']['repo_id']:
798 800 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
799 801 % elif c.template_context['search_context']['repo_group_id']:
800 802 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
801 803 % else:
802 804 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
803 805 % endif
804 806 </div>
805 807 </li>
806 808
807 809 ## ROOT MENU
808 810 <li class="${h.is_active('home', active)}">
809 811 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
810 812 <div class="menulabel">${_('Home')}</div>
811 813 </a>
812 814 </li>
813 815
814 816 %if c.rhodecode_user.username != h.DEFAULT_USER:
815 817 <li class="${h.is_active('journal', active)}">
816 818 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
817 819 <div class="menulabel">${_('Journal')}</div>
818 820 </a>
819 821 </li>
820 822 %else:
821 823 <li class="${h.is_active('journal', active)}">
822 824 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
823 825 <div class="menulabel">${_('Public journal')}</div>
824 826 </a>
825 827 </li>
826 828 %endif
827 829
828 830 <li class="${h.is_active('gists', active)}">
829 831 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
830 832 <div class="menulabel">${_('Gists')}</div>
831 833 </a>
832 834 </li>
833 835
834 836 % if c.is_super_admin or c.is_delegated_admin:
835 837 <li class="${h.is_active('admin', active)}">
836 838 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
837 839 <div class="menulabel">${_('Admin')} </div>
838 840 </a>
839 841 </li>
840 842 % endif
841 843
842 844 ## render extra user menu
843 845 ${usermenu(active=(active=='my_account'))}
844 846
845 847 </ul>
846 848
847 849 <script type="text/javascript">
848 850 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
849 851
850 852 var formatRepoResult = function(result, container, query, escapeMarkup) {
851 853 return function(data, escapeMarkup) {
852 854 if (!data.repo_id){
853 855 return data.text; // optgroup text Repositories
854 856 }
855 857
856 858 var tmpl = '';
857 859 var repoType = data['repo_type'];
858 860 var repoName = data['text'];
859 861
860 862 if(data && data.type == 'repo'){
861 863 if(repoType === 'hg'){
862 864 tmpl += '<i class="icon-hg"></i> ';
863 865 }
864 866 else if(repoType === 'git'){
865 867 tmpl += '<i class="icon-git"></i> ';
866 868 }
867 869 else if(repoType === 'svn'){
868 870 tmpl += '<i class="icon-svn"></i> ';
869 871 }
870 872 if(data['private']){
871 873 tmpl += '<i class="icon-lock" ></i> ';
872 874 }
873 875 else if(visualShowPublicIcon){
874 876 tmpl += '<i class="icon-unlock-alt"></i> ';
875 877 }
876 878 }
877 879 tmpl += escapeMarkup(repoName);
878 880 return tmpl;
879 881
880 882 }(result, escapeMarkup);
881 883 };
882 884
883 885 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
884 886 return function(data, escapeMarkup) {
885 887 if (!data.repo_group_id){
886 888 return data.text; // optgroup text Repositories
887 889 }
888 890
889 891 var tmpl = '';
890 892 var repoGroupName = data['text'];
891 893
892 894 if(data){
893 895
894 896 tmpl += '<i class="icon-repo-group"></i> ';
895 897
896 898 }
897 899 tmpl += escapeMarkup(repoGroupName);
898 900 return tmpl;
899 901
900 902 }(result, escapeMarkup);
901 903 };
902 904
903 905 var escapeRegExChars = function (value) {
904 906 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
905 907 };
906 908
907 909 var getRepoIcon = function(repo_type) {
908 910 if (repo_type === 'hg') {
909 911 return '<i class="icon-hg"></i> ';
910 912 }
911 913 else if (repo_type === 'git') {
912 914 return '<i class="icon-git"></i> ';
913 915 }
914 916 else if (repo_type === 'svn') {
915 917 return '<i class="icon-svn"></i> ';
916 918 }
917 919 return ''
918 920 };
919 921
920 922 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
921 923
922 924 if (value.split(':').length === 2) {
923 925 value = value.split(':')[1]
924 926 }
925 927
926 928 var searchType = data['type'];
927 929 var searchSubType = data['subtype'];
928 930 var valueDisplay = data['value_display'];
929 931 var valueIcon = data['value_icon'];
930 932
931 933 var pattern = '(' + escapeRegExChars(value) + ')';
932 934
933 935 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
934 936
935 937 // highlight match
936 938 if (searchType != 'text') {
937 939 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
938 940 }
939 941
940 942 var icon = '';
941 943
942 944 if (searchType === 'hint') {
943 945 icon += '<i class="icon-repo-group"></i> ';
944 946 }
945 947 // full text search/hints
946 948 else if (searchType === 'search') {
947 949 if (valueIcon === undefined) {
948 950 icon += '<i class="icon-more"></i> ';
949 951 } else {
950 952 icon += valueIcon + ' ';
951 953 }
952 954
953 955 if (searchSubType !== undefined && searchSubType == 'repo') {
954 956 valueDisplay += '<div class="pull-right tag">repository</div>';
955 957 }
956 958 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
957 959 valueDisplay += '<div class="pull-right tag">repo group</div>';
958 960 }
959 961 }
960 962 // repository
961 963 else if (searchType === 'repo') {
962 964
963 965 var repoIcon = getRepoIcon(data['repo_type']);
964 966 icon += repoIcon;
965 967
966 968 if (data['private']) {
967 969 icon += '<i class="icon-lock" ></i> ';
968 970 }
969 971 else if (visualShowPublicIcon) {
970 972 icon += '<i class="icon-unlock-alt"></i> ';
971 973 }
972 974 }
973 975 // repository groups
974 976 else if (searchType === 'repo_group') {
975 977 icon += '<i class="icon-repo-group"></i> ';
976 978 }
977 979 // user group
978 980 else if (searchType === 'user_group') {
979 981 icon += '<i class="icon-group"></i> ';
980 982 }
981 983 // user
982 984 else if (searchType === 'user') {
983 985 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
984 986 }
985 987 // pull request
986 988 else if (searchType === 'pull_request') {
987 989 icon += '<i class="icon-merge"></i> ';
988 990 }
989 991 // commit
990 992 else if (searchType === 'commit') {
991 993 var repo_data = data['repo_data'];
992 994 var repoIcon = getRepoIcon(repo_data['repository_type']);
993 995 if (repoIcon) {
994 996 icon += repoIcon;
995 997 } else {
996 998 icon += '<i class="icon-tag"></i>';
997 999 }
998 1000 }
999 1001 // file
1000 1002 else if (searchType === 'file') {
1001 1003 var repo_data = data['repo_data'];
1002 1004 var repoIcon = getRepoIcon(repo_data['repository_type']);
1003 1005 if (repoIcon) {
1004 1006 icon += repoIcon;
1005 1007 } else {
1006 1008 icon += '<i class="icon-tag"></i>';
1007 1009 }
1008 1010 }
1009 1011 // generic text
1010 1012 else if (searchType === 'text') {
1011 1013 icon = '';
1012 1014 }
1013 1015
1014 1016 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1015 1017 return tmpl.format(icon, valueDisplay);
1016 1018 };
1017 1019
1018 1020 var handleSelect = function(element, suggestion) {
1019 1021 if (suggestion.type === "hint") {
1020 1022 // we skip action
1021 1023 $('#main_filter').focus();
1022 1024 }
1023 1025 else if (suggestion.type === "text") {
1024 1026 // we skip action
1025 1027 $('#main_filter').focus();
1026 1028
1027 1029 } else {
1028 1030 window.location = suggestion['url'];
1029 1031 }
1030 1032 };
1031 1033
1032 1034 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1033 1035 if (queryLowerCase.split(':').length === 2) {
1034 1036 queryLowerCase = queryLowerCase.split(':')[1]
1035 1037 }
1036 1038 if (suggestion.type === "text") {
1037 1039 // special case we don't want to "skip" display for
1038 1040 return true
1039 1041 }
1040 1042 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1041 1043 };
1042 1044
1043 1045 var cleanContext = {
1044 1046 repo_view_type: null,
1045 1047
1046 1048 repo_id: null,
1047 1049 repo_name: "",
1048 1050
1049 1051 repo_group_id: null,
1050 1052 repo_group_name: null
1051 1053 };
1052 1054 var removeGoToFilter = function () {
1053 1055 $('.searchTagHidable').hide();
1054 1056 $('#main_filter').autocomplete(
1055 1057 'setOptions', {params:{search_context: cleanContext}});
1056 1058 };
1057 1059
1058 1060 $('#main_filter').autocomplete({
1059 1061 serviceUrl: pyroutes.url('goto_switcher_data'),
1060 1062 params: {
1061 1063 "search_context": templateContext.search_context
1062 1064 },
1063 1065 minChars:2,
1064 1066 maxHeight:400,
1065 1067 deferRequestBy: 300, //miliseconds
1066 1068 tabDisabled: true,
1067 1069 autoSelectFirst: false,
1068 1070 containerClass: 'autocomplete-qfilter-suggestions',
1069 1071 formatResult: autocompleteMainFilterFormatResult,
1070 1072 lookupFilter: autocompleteMainFilterResult,
1071 1073 onSelect: function (element, suggestion) {
1072 1074 handleSelect(element, suggestion);
1073 1075 return false;
1074 1076 },
1075 1077 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1076 1078 if (jqXHR !== 'abort') {
1077 1079 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1078 1080 SwalNoAnimation.fire({
1079 1081 icon: 'error',
1080 1082 title: _gettext('Error during search operation'),
1081 1083 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1082 1084 }).then(function(result) {
1083 1085 window.location.reload();
1084 1086 })
1085 1087 }
1086 1088 },
1087 1089 onSearchStart: function (params) {
1088 1090 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1089 1091 },
1090 1092 onSearchComplete: function (query, suggestions) {
1091 1093 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1092 1094 },
1093 1095 });
1094 1096
1095 1097 showMainFilterBox = function () {
1096 1098 $('#main_filter_help').toggle();
1097 1099 };
1098 1100
1099 1101 $('#main_filter').on('keydown.autocomplete', function (e) {
1100 1102
1101 1103 var BACKSPACE = 8;
1102 1104 var el = $(e.currentTarget);
1103 1105 if(e.which === BACKSPACE){
1104 1106 var inputVal = el.val();
1105 1107 if (inputVal === ""){
1106 1108 removeGoToFilter()
1107 1109 }
1108 1110 }
1109 1111 });
1110 1112
1111 1113 var dismissNotice = function(noticeId) {
1112 1114
1113 1115 var url = pyroutes.url('user_notice_dismiss',
1114 1116 {"user_id": templateContext.rhodecode_user.user_id});
1115 1117
1116 1118 var postData = {
1117 1119 'csrf_token': CSRF_TOKEN,
1118 1120 'notice_id': noticeId,
1119 1121 };
1120 1122
1121 1123 var success = function(response) {
1122 1124 $('#notice-message-' + noticeId).remove();
1123 1125 return false;
1124 1126 };
1125 1127 var failure = function(data, textStatus, xhr) {
1126 1128 alert("error processing request: " + textStatus);
1127 1129 return false;
1128 1130 };
1129 1131 ajaxPOST(url, postData, success, failure);
1130 1132 }
1131 1133 </script>
1132 1134 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1133 1135 </%def>
1134 1136
1135 1137 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1136 1138 <div class="modal-dialog">
1137 1139 <div class="modal-content">
1138 1140 <div class="modal-header">
1139 1141 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1140 1142 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1141 1143 </div>
1142 1144 <div class="modal-body">
1143 1145 <div class="block-left">
1144 1146 <table class="keyboard-mappings">
1145 1147 <tbody>
1146 1148 <tr>
1147 1149 <th></th>
1148 1150 <th>${_('Site-wide shortcuts')}</th>
1149 1151 </tr>
1150 1152 <%
1151 1153 elems = [
1152 1154 ('/', 'Use quick search box'),
1153 1155 ('g h', 'Goto home page'),
1154 1156 ('g g', 'Goto my private gists page'),
1155 1157 ('g G', 'Goto my public gists page'),
1156 1158 ('g 0-9', 'Goto bookmarked items from 0-9'),
1157 1159 ('n r', 'New repository page'),
1158 1160 ('n g', 'New gist page'),
1159 1161 ]
1160 1162 %>
1161 1163 %for key, desc in elems:
1162 1164 <tr>
1163 1165 <td class="keys">
1164 1166 <span class="key tag">${key}</span>
1165 1167 </td>
1166 1168 <td>${desc}</td>
1167 1169 </tr>
1168 1170 %endfor
1169 1171 </tbody>
1170 1172 </table>
1171 1173 </div>
1172 1174 <div class="block-left">
1173 1175 <table class="keyboard-mappings">
1174 1176 <tbody>
1175 1177 <tr>
1176 1178 <th></th>
1177 1179 <th>${_('Repositories')}</th>
1178 1180 </tr>
1179 1181 <%
1180 1182 elems = [
1181 1183 ('g s', 'Goto summary page'),
1182 1184 ('g c', 'Goto changelog page'),
1183 1185 ('g f', 'Goto files page'),
1184 1186 ('g F', 'Goto files page with file search activated'),
1185 1187 ('g p', 'Goto pull requests page'),
1186 1188 ('g o', 'Goto repository settings'),
1187 1189 ('g O', 'Goto repository access permissions settings'),
1188 1190 ]
1189 1191 %>
1190 1192 %for key, desc in elems:
1191 1193 <tr>
1192 1194 <td class="keys">
1193 1195 <span class="key tag">${key}</span>
1194 1196 </td>
1195 1197 <td>${desc}</td>
1196 1198 </tr>
1197 1199 %endfor
1198 1200 </tbody>
1199 1201 </table>
1200 1202 </div>
1201 1203 </div>
1202 1204 <div class="modal-footer">
1203 1205 </div>
1204 1206 </div><!-- /.modal-content -->
1205 1207 </div><!-- /.modal-dialog -->
1206 1208 </div><!-- /.modal -->
@@ -1,156 +1,169 b''
1 1 ## snippet for displaying default permission box
2 2 ## usage:
3 3 ## <%namespace name="dpb" file="/base/default_perms_box.mako"/>
4 4 ## ${dpb.default_perms_box(<url_to_form>)}
5 5 ## ${dpb.default_perms_radios()}
6 <%namespace name="base" file="/base/base.mako"/>
6 7
7 8 <%def name="default_perms_radios(global_permissions_template = False, suffix='', **kwargs)">
8 9 <div class="main-content-full-width">
9 10 <div class="panel panel-default">
10 11
11 12 ## displayed according to checkbox selection
12 13 <div class="panel-heading">
13 14 %if not global_permissions_template:
14 <h3 class="inherit_overlay_default panel-title">${_('Inherited Permissions')}</h3>
15 <h3 class="inherit_overlay panel-title">${_('Custom Permissions')}</h3>
15 <h3 class="inherit_overlay_default panel-title">
16 % if hasattr(c, 'user'):
17 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')} &nbsp;-
18 % endif
19 ${_('Inherited Permissions')}
20 </h3>
21 <h3 class="inherit_overlay panel-title">
22 % if hasattr(c, 'user'):
23 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')} &nbsp;-
24 % endif
25 ${_('Custom Permissions')}
26 </h3>
16 27 %else:
17 <h3 class="panel-title">${_('Default Global Permissions')}</h3>
28 <h3 class="panel-title">
29 ${_('Default Global Permissions')}
30 </h3>
18 31 %endif
19 32 </div>
20 33
21 34 <div class="panel-body">
22 35 %if global_permissions_template:
23 36 <p>${_('The following options configure the default permissions each user or group will inherit. You can override these permissions for each individual user or user group using individual permissions settings.')}</p>
24 37 %endif
25 38 <div class="field">
26 39 <div class="label">
27 40 <label for="default_repo_create${suffix}">${_('Repository Creation')}:</label>
28 41 </div>
29 42 <div class="radios">
30 43 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[1][0], label=c.repo_create_choices[1][1], **kwargs)}
31 44 ${h.radio('default_repo_create' + suffix, c.repo_create_choices[0][0], label=c.repo_create_choices[0][1], **kwargs)}
32 45 <span class="help-block">${_('Permission to create root level repositories. When disabled, users can still create repositories inside their own repository groups.')}</span>
33 46 </div>
34 47 </div>
35 48 <div class="field">
36 49 <div class="label">
37 50 <label for="default_repo_create_on_write${suffix}">${_('Repository Creation With Group Write Access')}:</label>
38 51 </div>
39 52 <div class="radios">
40 53 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[1][0], label=c.repo_create_on_write_choices[1][1], **kwargs)}
41 54 ${h.radio('default_repo_create_on_write' + suffix, c.repo_create_on_write_choices[0][0], label=c.repo_create_on_write_choices[0][1], **kwargs)}
42 55 <span class="help-block">${_('Write permission given on a repository group will allow creating repositories inside that group.')}</span>
43 56 </div>
44 57 </div>
45 58 <div class="field">
46 59 <div class="label">
47 60 <label for="default_fork_create${suffix}">${_('Repository Forking')}:</label>
48 61 </div>
49 62 <div class="radios">
50 63 ${h.radio('default_fork_create' + suffix, c.fork_choices[1][0], label=c.fork_choices[1][1], **kwargs)}
51 64 ${h.radio('default_fork_create' + suffix, c.fork_choices[0][0], label=c.fork_choices[0][1], **kwargs)}
52 65 <span class="help-block">${_('Permission to create root level repository forks. When disabled, users can still fork repositories inside their own repository groups.')}</span>
53 66 </div>
54 67 </div>
55 68 <div class="field">
56 69 <div class="label">
57 70 <label for="default_repo_group_create${suffix}">${_('Repository Group Creation')}:</label>
58 71 </div>
59 72 <div class="radios">
60 73 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[1][0], label=c.repo_group_create_choices[1][1], **kwargs)}
61 74 ${h.radio('default_repo_group_create' + suffix, c.repo_group_create_choices[0][0], label=c.repo_group_create_choices[0][1], **kwargs)}
62 75 <span class="help-block">${_('Permission to create root level repository groups. When disabled, repository group admins can still create repository subgroups within their repository groups.')}</span>
63 76 </div>
64 77 </div>
65 78 <div class="field">
66 79 <div class="label">
67 80 <label for="default_user_group_create${suffix}">${_('User Group Creation')}:</label>
68 81 </div>
69 82 <div class="radios">
70 83 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[1][0], label=c.user_group_create_choices[1][1], **kwargs)}
71 84 ${h.radio('default_user_group_create' + suffix, c.user_group_create_choices[0][0], label=c.user_group_create_choices[0][1], **kwargs)}
72 85 <span class="help-block">${_('Permission to allow user group creation.')}</span>
73 86 </div>
74 87 </div>
75 88
76 89 <div class="field">
77 90 <div class="label">
78 91 <label for="default_inherit_default_permissions${suffix}">${_('Inherit Permissions From The Default User')}:</label>
79 92 </div>
80 93 <div class="radios">
81 94 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[1][0], label=c.inherit_default_permission_choices[1][1], **kwargs)}
82 95 ${h.radio('default_inherit_default_permissions' + suffix, c.inherit_default_permission_choices[0][0], label=c.inherit_default_permission_choices[0][1], **kwargs)}
83 96 <span class="help-block">${_('Inherit default permissions from the default user. Turn off this option to force explicit permissions for users, even if they are more restrictive than the default user permissions.')}</span>
84 97 </div>
85 98 </div>
86 99
87 100 <div class="buttons">
88 101 ${h.submit('save',_('Save'),class_="btn")}
89 102 ${h.reset('reset',_('Reset'),class_="btn")}
90 103 </div>
91 104 </div>
92 105 </div>
93 106 </div>
94 107 </%def>
95 108
96 109 <%def name="default_perms_box(form_url)">
97 110 ${h.secure_form(form_url, request=request)}
98 111 <div class="form">
99 112 <div class="fields">
100 113 <div class="field panel panel-default panel-body">
101 114 <div class="label label-checkbox">
102 115 <label for="inherit_default_permissions">${_('Inherit from default settings')}:</label>
103 116 </div>
104 117 <div class="checkboxes">
105 118 ${h.checkbox('inherit_default_permissions',value=True)}
106 119 <span class="help-block">
107 120 ${h.literal(_('Select to inherit permissions from %s permissions settings, '
108 121 'including default IP address whitelist and inheritance of \npermission by members of user groups.')
109 122 % h.link_to('default user', h.route_path('admin_permissions_global')))}
110 123 </span>
111 124 </div>
112 125 </div>
113 126
114 127 ## INHERITED permissions == the user permissions in admin
115 128 ## if inherit checkbox is set this is displayed in non-edit mode
116 129 <div class="inherit_overlay_default">
117 130 ${default_perms_radios(global_permissions_template = False, suffix='_inherited', disabled="disabled")}
118 131 </div>
119 132
120 133 ## CUSTOM permissions
121 134 <div class="inherit_overlay">
122 135 ${default_perms_radios(global_permissions_template = False)}
123 136 </div>
124 137 </div>
125 138 </div>
126 139 ${h.end_form()}
127 140
128 141
129 142 ## JS
130 143 <script>
131 144 var show_custom_perms = function(inherit_default){
132 145 if(inherit_default) {
133 146 $('.inherit_overlay_default').show();
134 147 $('.inherit_overlay').hide();
135 148 }
136 149 else {
137 150 $('.inherit_overlay').show();
138 151 $('.inherit_overlay_default').hide();
139 152 }
140 153 };
141 154 $(document).ready(function(e){
142 155 var inherit_checkbox = $('#inherit_default_permissions');
143 156 var defaults = inherit_checkbox.prop('checked');
144 157 show_custom_perms(defaults);
145 158 inherit_checkbox.on('change', function(){
146 159 if($(this).prop('checked')){
147 160 show_custom_perms(true);
148 161 }
149 162 else{
150 163 show_custom_perms(false);
151 164 }
152 165 })
153 166 })
154 167 </script>
155 168
156 169 </%def>
@@ -1,372 +1,377 b''
1 1 ## snippet for displaying permissions overview for users
2 2 ## usage:
3 3 ## <%namespace name="p" file="/base/perms_summary.mako"/>
4 4 ## ${p.perms_summary(c.perm_user.permissions)}
5 <%namespace name="base" file="/base/base.mako"/>
5 6
6 7 <%def name="perms_summary(permissions, show_all=False, actions=True, side_link=None)">
7 8 <% section_to_label = {
8 'global': 'Global Permissions',
9 'repository_branches': 'Repository Branch Rules',
10 'repositories': 'Repository Access Permissions',
11 'user_groups': 'User Group Permissions',
12 'repositories_groups': 'Repository Group Permissions',
9 'global': 'Global Permissions Summary',
10 'repository_branches': 'Repository Branch Rules Summary',
11 'repositories': 'Repository Access Permissions Summary',
12 'user_groups': 'User Group Permissions Summary',
13 'repositories_groups': 'Repository Group Permissions Summary',
13 14 } %>
14 15
15 16 <div id="perms" class="table fields">
16 17 %for section in sorted(permissions.keys(), key=lambda item: {'global': 0, 'repository_branches': 1}.get(item, 1000)):
17 18 <% total_counter = 0 %>
18 19
19 20 <div class="panel panel-default">
20 21 <div class="panel-heading" id="${section.replace("_","-")}-permissions">
21 <h3 class="panel-title">${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
22 <h3 class="panel-title">
23 % if hasattr(c, 'user'):
24 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')} &nbsp;-
25 % endif
26 ${section_to_label.get(section, section)} - <span id="total_count_${section}"></span>
22 27 <a class="permalink" href="#${section.replace("_","-")}-permissions"> ΒΆ</a>
23 28 </h3>
24 29 % if side_link:
25 30 <div class="pull-right">
26 31 <a href="${side_link}">${_('in JSON format')}</a>
27 32 </div>
28 33 % endif
29 34 </div>
30 35 <div class="panel-body">
31 36 <div class="perms_section_head field">
32 37 <div class="radios">
33 38 % if section == 'repository_branches':
34 39 <span class="permissions_boxes">
35 40 <span class="desc">${_('show')}: </span>
36 41 ${h.checkbox('perms_filter_none_%s' % section, 'none', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
37 42 ${h.checkbox('perms_filter_merge_%s' % section, 'merge', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='merge')} <label for="${'perms_filter_merge_{}'.format(section)}"><span class="perm_tag merge">${_('merge')}</span></label>
38 43 ${h.checkbox('perms_filter_push_%s' % section, 'push', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push')} <label for="${'perms_filter_push_{}'.format(section)}"> <span class="perm_tag push">${_('push')}</span></label>
39 44 ${h.checkbox('perms_filter_push_force_%s' % section, 'push_force', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='push_force')} <label for="${'perms_filter_push_force_{}'.format(section)}"><span class="perm_tag push_force">${_('push force')}</span></label>
40 45 </span>
41 46 % elif section != 'global':
42 47 <span class="permissions_boxes">
43 48 <span class="desc">${_('show')}: </span>
44 49 ${h.checkbox('perms_filter_none_%s' % section, 'none', '', class_='perm_filter filter_%s' % section, section=section, perm_type='none')} <label for="${'perms_filter_none_{}'.format(section)}"><span class="perm_tag none">${_('none')}</span></label>
45 50 ${h.checkbox('perms_filter_read_%s' % section, 'read', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='read')} <label for="${'perms_filter_read_{}'.format(section)}"><span class="perm_tag read">${_('read')}</span></label>
46 51 ${h.checkbox('perms_filter_write_%s' % section, 'write', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='write')} <label for="${'perms_filter_write_{}'.format(section)}"> <span class="perm_tag write">${_('write')}</span></label>
47 52 ${h.checkbox('perms_filter_admin_%s' % section, 'admin', 'checked', class_='perm_filter filter_%s' % section, section=section, perm_type='admin')} <label for="${'perms_filter_admin_{}'.format(section)}"><span class="perm_tag admin">${_('admin')}</span></label>
48 53 </span>
49 54 % endif
50 55
51 56 </div>
52 57 </div>
53 58 <div class="field">
54 59 %if not permissions[section]:
55 60 <p class="empty_data help-block">${_('No permissions defined')}</p>
56 61 %else:
57 62 <div id='tbl_list_wrap_${section}'>
58 63 <table id="tbl_list_${section}" class="rctable">
59 64 ## global permission box
60 65 %if section == 'global':
61 66 <thead>
62 67 <tr>
63 68 <th colspan="2" class="left">${_('Permission')}</th>
64 69 %if actions:
65 70 <th colspan="2">${_('Edit Permission')}</th>
66 71 %endif
67 72 </thead>
68 73 <tbody>
69 74
70 75 <%
71 76 def get_section_perms(prefix, opts):
72 77 _selected = []
73 78 for op in opts:
74 79 if op.startswith(prefix) and not op.startswith('hg.create.write_on_repogroup'):
75 80 _selected.append(op)
76 81 admin = 'hg.admin' in opts
77 82 _selected_vals = [x.partition(prefix)[-1] for x in _selected]
78 83 return admin, _selected_vals, _selected
79 84 %>
80 85
81 86 <%def name="glob(lbl, val, val_lbl=None, edit_url=None, edit_global_url=None)">
82 87 <tr>
83 88 <td class="td-tags">
84 89 ${lbl}
85 90 </td>
86 91 <td class="td-tags">
87 92 %if val[0]:
88 93 %if not val_lbl:
89 94 ## super-admin case
90 95 True
91 96 %else:
92 97 <span class="perm_tag admin">${val_lbl}.admin</span>
93 98 %endif
94 99 %else:
95 100 %if not val_lbl:
96 101 ${{'false': False,
97 102 'true': True,
98 103 'none': False,
99 104 'repository': True}.get(val[1][0] if 0 < len(val[1]) else 'false')}
100 105 %else:
101 106 <span class="perm_tag ${val[1][0]}">${val_lbl}.${val[1][0]}</span>
102 107 %endif
103 108 %endif
104 109 </td>
105 110 %if actions:
106 111
107 112 % if edit_url or edit_global_url:
108 113
109 114 <td class="td-action">
110 115 % if edit_url:
111 116 <a href="${edit_url}">${_('edit')}</a>
112 117 % else:
113 118 -
114 119 % endif
115 120 </td>
116 121
117 122 <td class="td-action">
118 123 % if edit_global_url:
119 124 <a href="${edit_global_url}">${_('edit global')}</a>
120 125 % else:
121 126 -
122 127 % endif
123 128 </td>
124 129
125 130 % else:
126 131 <td class="td-action"></td>
127 132 <td class="td-action">
128 133 <a href="${h.route_path('admin_permissions_global')}">${_('edit global')}</a>
129 134 <td class="td-action">
130 135 % endif
131 136
132 137 %endif
133 138 </tr>
134 139 </%def>
135 140
136 141 ${glob(_('Repository default permission'), get_section_perms('repository.', permissions[section]), 'repository',
137 142 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
138 143
139 144 ${glob(_('Repository group default permission'), get_section_perms('group.', permissions[section]), 'group',
140 145 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
141 146
142 147 ${glob(_('User group default permission'), get_section_perms('usergroup.', permissions[section]), 'usergroup',
143 148 edit_url=None, edit_global_url=h.route_path('admin_permissions_object'))}
144 149
145 150 ${glob(_('Super-admin'), get_section_perms('hg.admin', permissions[section]),
146 151 edit_url=h.route_path('user_edit', user_id=c.user.user_id, _anchor='admin'), edit_global_url=None)}
147 152
148 153 ${glob(_('Inherit permissions'), get_section_perms('hg.inherit_default_perms.', permissions[section]),
149 154 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=None)}
150 155
151 156 ${glob(_('Create repositories'), get_section_perms('hg.create.', permissions[section]),
152 157 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
153 158
154 159 ${glob(_('Fork repositories'), get_section_perms('hg.fork.', permissions[section]),
155 160 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
156 161
157 162 ${glob(_('Create repository groups'), get_section_perms('hg.repogroup.create.', permissions[section]),
158 163 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
159 164
160 165 ${glob(_('Create user groups'), get_section_perms('hg.usergroup.create.', permissions[section]),
161 166 edit_url=h.route_path('user_edit_global_perms', user_id=c.user.user_id), edit_global_url=h.route_path('admin_permissions_object'))}
162 167
163 168 </tbody>
164 169 ## Branch perms
165 170 %elif section == 'repository_branches':
166 171 <thead>
167 172 <tr>
168 173 <th>${_('Name')}</th>
169 174 <th>${_('Pattern')}</th>
170 175 <th>${_('Permission')}</th>
171 176 %if actions:
172 177 <th>${_('Edit Branch Permission')}</th>
173 178 %endif
174 179 </thead>
175 180 <tbody class="section_${section}">
176 181 <%
177 182 def name_sorter(permissions):
178 183 def custom_sorter(item):
179 184 return item[0]
180 185 return sorted(permissions, key=custom_sorter)
181 186
182 187 def branch_sorter(permissions):
183 188 def custom_sorter(item):
184 189 ## none, merge, push, push_force
185 190 section = item[1].split('.')[-1]
186 191 section_importance = {'none': u'0',
187 192 'merge': u'1',
188 193 'push': u'2',
189 194 'push_force': u'3'}.get(section)
190 195 ## sort by importance + name
191 196 return section_importance + item[0]
192 197 return sorted(permissions, key=custom_sorter)
193 198 %>
194 199 %for k, section_perms in name_sorter(permissions[section].items()):
195 200 ## for display purposes, for non super-admins we need to check if shown
196 201 ## repository is actually accessible for user
197 202 <% repo_perm = permissions['repositories'][k] %>
198 203 % if repo_perm == 'repository.none' and not c.rhodecode_user.is_admin:
199 204 ## skip this entry
200 205 <% continue %>
201 206 % endif
202 207
203 208 <% total_counter +=1 %>
204 209 % for pattern, perm in branch_sorter(section_perms.items()):
205 210 <tr class="perm_row ${'{}_{}'.format(section, perm.split('.')[-1])}">
206 211 <td class="td-name">
207 212 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
208 213 </td>
209 214 <td>${pattern}</td>
210 215 <td class="td-tags">
211 216 ## TODO: calculate origin somehow
212 217 ## % for i, ((_pat, perm), origin) in enumerate((permissions[section].perm_origin_stack[k])):
213 218
214 219 <div>
215 220 <% i = 0 %>
216 221 <% origin = 'unknown' %>
217 222 <% _css_class = i > 0 and 'perm_overriden' or '' %>
218 223
219 224 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
220 225 ${perm}
221 226 ##(${origin})
222 227 </span>
223 228 </div>
224 229 ## % endfor
225 230 </td>
226 231 %if actions:
227 232 <td class="td-action">
228 233 <a href="${h.route_path('edit_repo_perms_branch',repo_name=k)}">${_('edit')}</a>
229 234 </td>
230 235 %endif
231 236 </tr>
232 237 % endfor
233 238 %endfor
234 239 </tbody>
235 240
236 241 ## Repos/Repo Groups/users groups perms
237 242 %else:
238 243
239 244 ## none/read/write/admin permissions on groups/repos etc
240 245 <thead>
241 246 <tr>
242 247 <th>${_('Name')}</th>
243 248 <th>${_('Permission')}</th>
244 249 %if actions:
245 250 <th>${_('Edit Permission')}</th>
246 251 %endif
247 252 </thead>
248 253 <tbody class="section_${section}">
249 254 <%
250 255 def sorter(permissions):
251 256 def custom_sorter(item):
252 257 ## read/write/admin
253 258 section = item[1].split('.')[-1]
254 259 section_importance = {'none': u'0',
255 260 'read': u'1',
256 261 'write':u'2',
257 262 'admin':u'3'}.get(section)
258 263 ## sort by group importance+name
259 264 return section_importance+item[0]
260 265 return sorted(permissions, key=custom_sorter)
261 266 %>
262 267 %for k, section_perm in sorter(permissions[section].items()):
263 268 <% perm_value = section_perm.split('.')[-1] %>
264 269 <% _css_class = 'display:none' if perm_value in ['none'] else '' %>
265 270
266 271 %if perm_value != 'none' or show_all:
267 272 <tr class="perm_row ${'{}_{}'.format(section, section_perm.split('.')[-1])}" style="${_css_class}">
268 273 <td class="td-name">
269 274 %if section == 'repositories':
270 275 <a href="${h.route_path('repo_summary',repo_name=k)}">${k}</a>
271 276 %elif section == 'repositories_groups':
272 277 <a href="${h.route_path('repo_group_home', repo_group_name=k)}">${k}</a>
273 278 %elif section == 'user_groups':
274 279 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${k}</a>
275 280 ${k}
276 281 %endif
277 282 </td>
278 283 <td class="td-tags">
279 284 %if hasattr(permissions[section], 'perm_origin_stack'):
280 285 <div>
281 286 %for i, (perm, origin, obj_id) in enumerate(reversed(permissions[section].perm_origin_stack[k])):
282 287 <% _css_class = i > 0 and 'perm_overriden' or '' %>
283 288 % if i > 0:
284 289 <div style="color: #979797">
285 290 <i class="icon-arrow_up"></i>
286 291 ${_('overridden by')}
287 292 <i class="icon-arrow_up"></i>
288 293 </div>
289 294 % endif
290 295
291 296 <div>
292 297 <span class="${_css_class} perm_tag ${perm.split('.')[-1]}">
293 298 ${perm} (${origin})
294 299 </span>
295 300 </div>
296 301
297 302 %endfor
298 303 </div>
299 304 %else:
300 305 <span class="perm_tag ${section_perm.split('.')[-1]}">${section_perm}</span>
301 306 %endif
302 307 </td>
303 308 %if actions:
304 309 <td class="td-action">
305 310 %if section == 'repositories':
306 311 <a href="${h.route_path('edit_repo_perms',repo_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
307 312 %elif section == 'repositories_groups':
308 313 <a href="${h.route_path('edit_repo_group_perms',repo_group_name=k,_anchor='permissions_manage')}">${_('edit')}</a>
309 314 %elif section == 'user_groups':
310 315 ##<a href="${h.route_path('edit_user_group',user_group_id=k)}">${_('edit')}</a>
311 316 %endif
312 317 </td>
313 318 %endif
314 319 </tr>
315 320 <% total_counter +=1 %>
316 321 %endif
317 322
318 323 %endfor
319 324
320 325 <tr id="empty_${section}" class="noborder" style="display:none;">
321 326 <td colspan="6">${_('No matching permission defined')}</td>
322 327 </tr>
323 328
324 329 </tbody>
325 330 %endif
326 331 </table>
327 332 </div>
328 333 %endif
329 334 </div>
330 335 </div>
331 336 </div>
332 337
333 338 <script>
334 339 $('#total_count_${section}').html(${total_counter})
335 340 </script>
336 341
337 342 %endfor
338 343 </div>
339 344
340 345 <script>
341 346 $(document).ready(function(){
342 347 var showEmpty = function(section){
343 348 var visible = $('.section_{0} tr.perm_row:visible'.format(section)).length;
344 349 if(visible === 0){
345 350 $('#empty_{0}'.format(section)).show();
346 351 }
347 352 else{
348 353 $('#empty_{0}'.format(section)).hide();
349 354 }
350 355 };
351 356
352 357 $('.perm_filter').on('change', function(e){
353 358 var self = this;
354 359 var section = $(this).attr('section');
355 360
356 361 var opts = {};
357 362 var elems = $('.filter_' + section).each(function(el){
358 363 var perm_type = $(this).attr('perm_type');
359 364 var checked = this.checked;
360 365 opts[perm_type] = checked;
361 366 if(checked){
362 367 $('.'+section+'_'+perm_type).show();
363 368 }
364 369 else{
365 370 $('.'+section+'_'+perm_type).hide();
366 371 }
367 372 });
368 373 showEmpty(section);
369 374 })
370 375 })
371 376 </script>
372 377 </%def>
General Comments 0
You need to be logged in to leave comments. Login now