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