##// END OF EJS Templates
pyramid: if possible fetch csrf tokens from pyramid session....
marcink -
r1918:de4c5093 default
parent child Browse files
Show More
@@ -1,2026 +1,2031 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import random
29 29 import hashlib
30 30 import StringIO
31 31 import urllib
32 32 import math
33 33 import logging
34 34 import re
35 35 import urlparse
36 36 import time
37 37 import string
38 38 import hashlib
39 39 from collections import OrderedDict
40 40
41 41 import pygments
42 42 import itertools
43 43 import fnmatch
44 44
45 45 from datetime import datetime
46 46 from functools import partial
47 47 from pygments.formatters.html import HtmlFormatter
48 48 from pygments import highlight as code_highlight
49 49 from pygments.lexers import (
50 50 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
51 51
52 52 from pyramid.threadlocal import get_current_request
53 53
54 54 from webhelpers.html import literal, HTML, escape
55 55 from webhelpers.html.tools import *
56 56 from webhelpers.html.builder import make_tag
57 57 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
58 58 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
59 59 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
60 60 submit, text, password, textarea, title, ul, xml_declaration, radio
61 61 from webhelpers.html.tools import auto_link, button_to, highlight, \
62 62 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
63 63 from webhelpers.pylonslib import Flash as _Flash
64 64 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 65 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 66 replace_whitespace, urlify, truncate, wrap_paragraphs
67 67 from webhelpers.date import time_ago_in_words
68 68 from webhelpers.paginate import Page as _Page
69 69 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 70 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 71 from webhelpers2.number import format_byte_size
72 72
73 73 from rhodecode.lib.action_parser import action_parser
74 74 from rhodecode.lib.ext_json import json
75 75 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 76 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 77 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 78 AttributeDict, safe_int, md5, md5_safe
79 79 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 80 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 81 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 82 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
83 83 from rhodecode.model.changeset_status import ChangesetStatusModel
84 84 from rhodecode.model.db import Permission, User, Repository
85 85 from rhodecode.model.repo_group import RepoGroupModel
86 86 from rhodecode.model.settings import IssueTrackerSettingsModel
87 87
88 88 log = logging.getLogger(__name__)
89 89
90 90
91 91 DEFAULT_USER = User.DEFAULT_USER
92 92 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
93 93
94 94
95 95 def url(*args, **kw):
96 96 from pylons import url as pylons_url
97 97 return pylons_url(*args, **kw)
98 98
99 99
100 100 def pylons_url_current(*args, **kw):
101 101 """
102 102 This function overrides pylons.url.current() which returns the current
103 103 path so that it will also work from a pyramid only context. This
104 104 should be removed once port to pyramid is complete.
105 105 """
106 106 from pylons import url as pylons_url
107 107 if not args and not kw:
108 108 request = get_current_request()
109 109 return request.path
110 110 return pylons_url.current(*args, **kw)
111 111
112 112 url.current = pylons_url_current
113 113
114 114
115 115 def url_replace(**qargs):
116 116 """ Returns the current request url while replacing query string args """
117 117
118 118 request = get_current_request()
119 119 new_args = request.GET.mixed()
120 120 new_args.update(qargs)
121 121 return url('', **new_args)
122 122
123 123
124 124 def asset(path, ver=None, **kwargs):
125 125 """
126 126 Helper to generate a static asset file path for rhodecode assets
127 127
128 128 eg. h.asset('images/image.png', ver='3923')
129 129
130 130 :param path: path of asset
131 131 :param ver: optional version query param to append as ?ver=
132 132 """
133 133 request = get_current_request()
134 134 query = {}
135 135 query.update(kwargs)
136 136 if ver:
137 137 query = {'ver': ver}
138 138 return request.static_path(
139 139 'rhodecode:public/{}'.format(path), _query=query)
140 140
141 141
142 142 default_html_escape_table = {
143 143 ord('&'): u'&amp;',
144 144 ord('<'): u'&lt;',
145 145 ord('>'): u'&gt;',
146 146 ord('"'): u'&quot;',
147 147 ord("'"): u'&#39;',
148 148 }
149 149
150 150
151 151 def html_escape(text, html_escape_table=default_html_escape_table):
152 152 """Produce entities within text."""
153 153 return text.translate(html_escape_table)
154 154
155 155
156 156 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
157 157 """
158 158 Truncate string ``s`` at the first occurrence of ``sub``.
159 159
160 160 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
161 161 """
162 162 suffix_if_chopped = suffix_if_chopped or ''
163 163 pos = s.find(sub)
164 164 if pos == -1:
165 165 return s
166 166
167 167 if inclusive:
168 168 pos += len(sub)
169 169
170 170 chopped = s[:pos]
171 171 left = s[pos:].strip()
172 172
173 173 if left and suffix_if_chopped:
174 174 chopped += suffix_if_chopped
175 175
176 176 return chopped
177 177
178 178
179 179 def shorter(text, size=20):
180 180 postfix = '...'
181 181 if len(text) > size:
182 182 return text[:size - len(postfix)] + postfix
183 183 return text
184 184
185 185
186 186 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
187 187 """
188 188 Reset button
189 189 """
190 190 _set_input_attrs(attrs, type, name, value)
191 191 _set_id_attr(attrs, id, name)
192 192 convert_boolean_attrs(attrs, ["disabled"])
193 193 return HTML.input(**attrs)
194 194
195 195 reset = _reset
196 196 safeid = _make_safe_id_component
197 197
198 198
199 199 def branding(name, length=40):
200 200 return truncate(name, length, indicator="")
201 201
202 202
203 203 def FID(raw_id, path):
204 204 """
205 205 Creates a unique ID for filenode based on it's hash of path and commit
206 206 it's safe to use in urls
207 207
208 208 :param raw_id:
209 209 :param path:
210 210 """
211 211
212 212 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
213 213
214 214
215 215 class _GetError(object):
216 216 """Get error from form_errors, and represent it as span wrapped error
217 217 message
218 218
219 219 :param field_name: field to fetch errors for
220 220 :param form_errors: form errors dict
221 221 """
222 222
223 223 def __call__(self, field_name, form_errors):
224 224 tmpl = """<span class="error_msg">%s</span>"""
225 225 if form_errors and field_name in form_errors:
226 226 return literal(tmpl % form_errors.get(field_name))
227 227
228 228 get_error = _GetError()
229 229
230 230
231 231 class _ToolTip(object):
232 232
233 233 def __call__(self, tooltip_title, trim_at=50):
234 234 """
235 235 Special function just to wrap our text into nice formatted
236 236 autowrapped text
237 237
238 238 :param tooltip_title:
239 239 """
240 240 tooltip_title = escape(tooltip_title)
241 241 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
242 242 return tooltip_title
243 243 tooltip = _ToolTip()
244 244
245 245
246 246 def files_breadcrumbs(repo_name, commit_id, file_path):
247 247 if isinstance(file_path, str):
248 248 file_path = safe_unicode(file_path)
249 249
250 250 # TODO: johbo: Is this always a url like path, or is this operating
251 251 # system dependent?
252 252 path_segments = file_path.split('/')
253 253
254 254 repo_name_html = escape(repo_name)
255 255 if len(path_segments) == 1 and path_segments[0] == '':
256 256 url_segments = [repo_name_html]
257 257 else:
258 258 url_segments = [
259 259 link_to(
260 260 repo_name_html,
261 261 url('files_home',
262 262 repo_name=repo_name,
263 263 revision=commit_id,
264 264 f_path=''),
265 265 class_='pjax-link')]
266 266
267 267 last_cnt = len(path_segments) - 1
268 268 for cnt, segment in enumerate(path_segments):
269 269 if not segment:
270 270 continue
271 271 segment_html = escape(segment)
272 272
273 273 if cnt != last_cnt:
274 274 url_segments.append(
275 275 link_to(
276 276 segment_html,
277 277 url('files_home',
278 278 repo_name=repo_name,
279 279 revision=commit_id,
280 280 f_path='/'.join(path_segments[:cnt + 1])),
281 281 class_='pjax-link'))
282 282 else:
283 283 url_segments.append(segment_html)
284 284
285 285 return literal('/'.join(url_segments))
286 286
287 287
288 288 class CodeHtmlFormatter(HtmlFormatter):
289 289 """
290 290 My code Html Formatter for source codes
291 291 """
292 292
293 293 def wrap(self, source, outfile):
294 294 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
295 295
296 296 def _wrap_code(self, source):
297 297 for cnt, it in enumerate(source):
298 298 i, t = it
299 299 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
300 300 yield i, t
301 301
302 302 def _wrap_tablelinenos(self, inner):
303 303 dummyoutfile = StringIO.StringIO()
304 304 lncount = 0
305 305 for t, line in inner:
306 306 if t:
307 307 lncount += 1
308 308 dummyoutfile.write(line)
309 309
310 310 fl = self.linenostart
311 311 mw = len(str(lncount + fl - 1))
312 312 sp = self.linenospecial
313 313 st = self.linenostep
314 314 la = self.lineanchors
315 315 aln = self.anchorlinenos
316 316 nocls = self.noclasses
317 317 if sp:
318 318 lines = []
319 319
320 320 for i in range(fl, fl + lncount):
321 321 if i % st == 0:
322 322 if i % sp == 0:
323 323 if aln:
324 324 lines.append('<a href="#%s%d" class="special">%*d</a>' %
325 325 (la, i, mw, i))
326 326 else:
327 327 lines.append('<span class="special">%*d</span>' % (mw, i))
328 328 else:
329 329 if aln:
330 330 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
331 331 else:
332 332 lines.append('%*d' % (mw, i))
333 333 else:
334 334 lines.append('')
335 335 ls = '\n'.join(lines)
336 336 else:
337 337 lines = []
338 338 for i in range(fl, fl + lncount):
339 339 if i % st == 0:
340 340 if aln:
341 341 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
342 342 else:
343 343 lines.append('%*d' % (mw, i))
344 344 else:
345 345 lines.append('')
346 346 ls = '\n'.join(lines)
347 347
348 348 # in case you wonder about the seemingly redundant <div> here: since the
349 349 # content in the other cell also is wrapped in a div, some browsers in
350 350 # some configurations seem to mess up the formatting...
351 351 if nocls:
352 352 yield 0, ('<table class="%stable">' % self.cssclass +
353 353 '<tr><td><div class="linenodiv" '
354 354 'style="background-color: #f0f0f0; padding-right: 10px">'
355 355 '<pre style="line-height: 125%">' +
356 356 ls + '</pre></div></td><td id="hlcode" class="code">')
357 357 else:
358 358 yield 0, ('<table class="%stable">' % self.cssclass +
359 359 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
360 360 ls + '</pre></div></td><td id="hlcode" class="code">')
361 361 yield 0, dummyoutfile.getvalue()
362 362 yield 0, '</td></tr></table>'
363 363
364 364
365 365 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
366 366 def __init__(self, **kw):
367 367 # only show these line numbers if set
368 368 self.only_lines = kw.pop('only_line_numbers', [])
369 369 self.query_terms = kw.pop('query_terms', [])
370 370 self.max_lines = kw.pop('max_lines', 5)
371 371 self.line_context = kw.pop('line_context', 3)
372 372 self.url = kw.pop('url', None)
373 373
374 374 super(CodeHtmlFormatter, self).__init__(**kw)
375 375
376 376 def _wrap_code(self, source):
377 377 for cnt, it in enumerate(source):
378 378 i, t = it
379 379 t = '<pre>%s</pre>' % t
380 380 yield i, t
381 381
382 382 def _wrap_tablelinenos(self, inner):
383 383 yield 0, '<table class="code-highlight %stable">' % self.cssclass
384 384
385 385 last_shown_line_number = 0
386 386 current_line_number = 1
387 387
388 388 for t, line in inner:
389 389 if not t:
390 390 yield t, line
391 391 continue
392 392
393 393 if current_line_number in self.only_lines:
394 394 if last_shown_line_number + 1 != current_line_number:
395 395 yield 0, '<tr>'
396 396 yield 0, '<td class="line">...</td>'
397 397 yield 0, '<td id="hlcode" class="code"></td>'
398 398 yield 0, '</tr>'
399 399
400 400 yield 0, '<tr>'
401 401 if self.url:
402 402 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
403 403 self.url, current_line_number, current_line_number)
404 404 else:
405 405 yield 0, '<td class="line"><a href="">%i</a></td>' % (
406 406 current_line_number)
407 407 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
408 408 yield 0, '</tr>'
409 409
410 410 last_shown_line_number = current_line_number
411 411
412 412 current_line_number += 1
413 413
414 414
415 415 yield 0, '</table>'
416 416
417 417
418 418 def extract_phrases(text_query):
419 419 """
420 420 Extracts phrases from search term string making sure phrases
421 421 contained in double quotes are kept together - and discarding empty values
422 422 or fully whitespace values eg.
423 423
424 424 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
425 425
426 426 """
427 427
428 428 in_phrase = False
429 429 buf = ''
430 430 phrases = []
431 431 for char in text_query:
432 432 if in_phrase:
433 433 if char == '"': # end phrase
434 434 phrases.append(buf)
435 435 buf = ''
436 436 in_phrase = False
437 437 continue
438 438 else:
439 439 buf += char
440 440 continue
441 441 else:
442 442 if char == '"': # start phrase
443 443 in_phrase = True
444 444 phrases.append(buf)
445 445 buf = ''
446 446 continue
447 447 elif char == ' ':
448 448 phrases.append(buf)
449 449 buf = ''
450 450 continue
451 451 else:
452 452 buf += char
453 453
454 454 phrases.append(buf)
455 455 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
456 456 return phrases
457 457
458 458
459 459 def get_matching_offsets(text, phrases):
460 460 """
461 461 Returns a list of string offsets in `text` that the list of `terms` match
462 462
463 463 >>> get_matching_offsets('some text here', ['some', 'here'])
464 464 [(0, 4), (10, 14)]
465 465
466 466 """
467 467 offsets = []
468 468 for phrase in phrases:
469 469 for match in re.finditer(phrase, text):
470 470 offsets.append((match.start(), match.end()))
471 471
472 472 return offsets
473 473
474 474
475 475 def normalize_text_for_matching(x):
476 476 """
477 477 Replaces all non alnum characters to spaces and lower cases the string,
478 478 useful for comparing two text strings without punctuation
479 479 """
480 480 return re.sub(r'[^\w]', ' ', x.lower())
481 481
482 482
483 483 def get_matching_line_offsets(lines, terms):
484 484 """ Return a set of `lines` indices (starting from 1) matching a
485 485 text search query, along with `context` lines above/below matching lines
486 486
487 487 :param lines: list of strings representing lines
488 488 :param terms: search term string to match in lines eg. 'some text'
489 489 :param context: number of lines above/below a matching line to add to result
490 490 :param max_lines: cut off for lines of interest
491 491 eg.
492 492
493 493 text = '''
494 494 words words words
495 495 words words words
496 496 some text some
497 497 words words words
498 498 words words words
499 499 text here what
500 500 '''
501 501 get_matching_line_offsets(text, 'text', context=1)
502 502 {3: [(5, 9)], 6: [(0, 4)]]
503 503
504 504 """
505 505 matching_lines = {}
506 506 phrases = [normalize_text_for_matching(phrase)
507 507 for phrase in extract_phrases(terms)]
508 508
509 509 for line_index, line in enumerate(lines, start=1):
510 510 match_offsets = get_matching_offsets(
511 511 normalize_text_for_matching(line), phrases)
512 512 if match_offsets:
513 513 matching_lines[line_index] = match_offsets
514 514
515 515 return matching_lines
516 516
517 517
518 518 def hsv_to_rgb(h, s, v):
519 519 """ Convert hsv color values to rgb """
520 520
521 521 if s == 0.0:
522 522 return v, v, v
523 523 i = int(h * 6.0) # XXX assume int() truncates!
524 524 f = (h * 6.0) - i
525 525 p = v * (1.0 - s)
526 526 q = v * (1.0 - s * f)
527 527 t = v * (1.0 - s * (1.0 - f))
528 528 i = i % 6
529 529 if i == 0:
530 530 return v, t, p
531 531 if i == 1:
532 532 return q, v, p
533 533 if i == 2:
534 534 return p, v, t
535 535 if i == 3:
536 536 return p, q, v
537 537 if i == 4:
538 538 return t, p, v
539 539 if i == 5:
540 540 return v, p, q
541 541
542 542
543 543 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
544 544 """
545 545 Generator for getting n of evenly distributed colors using
546 546 hsv color and golden ratio. It always return same order of colors
547 547
548 548 :param n: number of colors to generate
549 549 :param saturation: saturation of returned colors
550 550 :param lightness: lightness of returned colors
551 551 :returns: RGB tuple
552 552 """
553 553
554 554 golden_ratio = 0.618033988749895
555 555 h = 0.22717784590367374
556 556
557 557 for _ in xrange(n):
558 558 h += golden_ratio
559 559 h %= 1
560 560 HSV_tuple = [h, saturation, lightness]
561 561 RGB_tuple = hsv_to_rgb(*HSV_tuple)
562 562 yield map(lambda x: str(int(x * 256)), RGB_tuple)
563 563
564 564
565 565 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
566 566 """
567 567 Returns a function which when called with an argument returns a unique
568 568 color for that argument, eg.
569 569
570 570 :param n: number of colors to generate
571 571 :param saturation: saturation of returned colors
572 572 :param lightness: lightness of returned colors
573 573 :returns: css RGB string
574 574
575 575 >>> color_hash = color_hasher()
576 576 >>> color_hash('hello')
577 577 'rgb(34, 12, 59)'
578 578 >>> color_hash('hello')
579 579 'rgb(34, 12, 59)'
580 580 >>> color_hash('other')
581 581 'rgb(90, 224, 159)'
582 582 """
583 583
584 584 color_dict = {}
585 585 cgenerator = unique_color_generator(
586 586 saturation=saturation, lightness=lightness)
587 587
588 588 def get_color_string(thing):
589 589 if thing in color_dict:
590 590 col = color_dict[thing]
591 591 else:
592 592 col = color_dict[thing] = cgenerator.next()
593 593 return "rgb(%s)" % (', '.join(col))
594 594
595 595 return get_color_string
596 596
597 597
598 598 def get_lexer_safe(mimetype=None, filepath=None):
599 599 """
600 600 Tries to return a relevant pygments lexer using mimetype/filepath name,
601 601 defaulting to plain text if none could be found
602 602 """
603 603 lexer = None
604 604 try:
605 605 if mimetype:
606 606 lexer = get_lexer_for_mimetype(mimetype)
607 607 if not lexer:
608 608 lexer = get_lexer_for_filename(filepath)
609 609 except pygments.util.ClassNotFound:
610 610 pass
611 611
612 612 if not lexer:
613 613 lexer = get_lexer_by_name('text')
614 614
615 615 return lexer
616 616
617 617
618 618 def get_lexer_for_filenode(filenode):
619 619 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
620 620 return lexer
621 621
622 622
623 623 def pygmentize(filenode, **kwargs):
624 624 """
625 625 pygmentize function using pygments
626 626
627 627 :param filenode:
628 628 """
629 629 lexer = get_lexer_for_filenode(filenode)
630 630 return literal(code_highlight(filenode.content, lexer,
631 631 CodeHtmlFormatter(**kwargs)))
632 632
633 633
634 634 def is_following_repo(repo_name, user_id):
635 635 from rhodecode.model.scm import ScmModel
636 636 return ScmModel().is_following_repo(repo_name, user_id)
637 637
638 638
639 639 class _Message(object):
640 640 """A message returned by ``Flash.pop_messages()``.
641 641
642 642 Converting the message to a string returns the message text. Instances
643 643 also have the following attributes:
644 644
645 645 * ``message``: the message text.
646 646 * ``category``: the category specified when the message was created.
647 647 """
648 648
649 649 def __init__(self, category, message):
650 650 self.category = category
651 651 self.message = message
652 652
653 653 def __str__(self):
654 654 return self.message
655 655
656 656 __unicode__ = __str__
657 657
658 658 def __html__(self):
659 659 return escape(safe_unicode(self.message))
660 660
661 661
662 662 class Flash(_Flash):
663 663
664 664 def pop_messages(self, request=None):
665 665 """Return all accumulated messages and delete them from the session.
666 666
667 667 The return value is a list of ``Message`` objects.
668 668 """
669 669 messages = []
670 670
671 671 if request:
672 672 session = request.session
673 673 else:
674 674 from pylons import session
675 675
676 676 # Pop the 'old' pylons flash messages. They are tuples of the form
677 677 # (category, message)
678 678 for cat, msg in session.pop(self.session_key, []):
679 679 messages.append(_Message(cat, msg))
680 680
681 681 # Pop the 'new' pyramid flash messages for each category as list
682 682 # of strings.
683 683 for cat in self.categories:
684 684 for msg in session.pop_flash(queue=cat):
685 685 messages.append(_Message(cat, msg))
686 686 # Map messages from the default queue to the 'notice' category.
687 687 for msg in session.pop_flash():
688 688 messages.append(_Message('notice', msg))
689 689
690 690 session.save()
691 691 return messages
692 692
693 693 def json_alerts(self, request=None):
694 694 payloads = []
695 695 messages = flash.pop_messages(request=request)
696 696 if messages:
697 697 for message in messages:
698 698 subdata = {}
699 699 if hasattr(message.message, 'rsplit'):
700 700 flash_data = message.message.rsplit('|DELIM|', 1)
701 701 org_message = flash_data[0]
702 702 if len(flash_data) > 1:
703 703 subdata = json.loads(flash_data[1])
704 704 else:
705 705 org_message = message.message
706 706 payloads.append({
707 707 'message': {
708 708 'message': u'{}'.format(org_message),
709 709 'level': message.category,
710 710 'force': True,
711 711 'subdata': subdata
712 712 }
713 713 })
714 714 return json.dumps(payloads)
715 715
716 716 flash = Flash()
717 717
718 718 #==============================================================================
719 719 # SCM FILTERS available via h.
720 720 #==============================================================================
721 721 from rhodecode.lib.vcs.utils import author_name, author_email
722 722 from rhodecode.lib.utils2 import credentials_filter, age as _age
723 723 from rhodecode.model.db import User, ChangesetStatus
724 724
725 725 age = _age
726 726 capitalize = lambda x: x.capitalize()
727 727 email = author_email
728 728 short_id = lambda x: x[:12]
729 729 hide_credentials = lambda x: ''.join(credentials_filter(x))
730 730
731 731
732 732 def age_component(datetime_iso, value=None, time_is_local=False):
733 733 title = value or format_date(datetime_iso)
734 734 tzinfo = '+00:00'
735 735
736 736 # detect if we have a timezone info, otherwise, add it
737 737 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
738 738 if time_is_local:
739 739 tzinfo = time.strftime("+%H:%M",
740 740 time.gmtime(
741 741 (datetime.now() - datetime.utcnow()).seconds + 1
742 742 )
743 743 )
744 744
745 745 return literal(
746 746 '<time class="timeago tooltip" '
747 747 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
748 748 datetime_iso, title, tzinfo))
749 749
750 750
751 751 def _shorten_commit_id(commit_id):
752 752 from rhodecode import CONFIG
753 753 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
754 754 return commit_id[:def_len]
755 755
756 756
757 757 def show_id(commit):
758 758 """
759 759 Configurable function that shows ID
760 760 by default it's r123:fffeeefffeee
761 761
762 762 :param commit: commit instance
763 763 """
764 764 from rhodecode import CONFIG
765 765 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
766 766
767 767 raw_id = _shorten_commit_id(commit.raw_id)
768 768 if show_idx:
769 769 return 'r%s:%s' % (commit.idx, raw_id)
770 770 else:
771 771 return '%s' % (raw_id, )
772 772
773 773
774 774 def format_date(date):
775 775 """
776 776 use a standardized formatting for dates used in RhodeCode
777 777
778 778 :param date: date/datetime object
779 779 :return: formatted date
780 780 """
781 781
782 782 if date:
783 783 _fmt = "%a, %d %b %Y %H:%M:%S"
784 784 return safe_unicode(date.strftime(_fmt))
785 785
786 786 return u""
787 787
788 788
789 789 class _RepoChecker(object):
790 790
791 791 def __init__(self, backend_alias):
792 792 self._backend_alias = backend_alias
793 793
794 794 def __call__(self, repository):
795 795 if hasattr(repository, 'alias'):
796 796 _type = repository.alias
797 797 elif hasattr(repository, 'repo_type'):
798 798 _type = repository.repo_type
799 799 else:
800 800 _type = repository
801 801 return _type == self._backend_alias
802 802
803 803 is_git = _RepoChecker('git')
804 804 is_hg = _RepoChecker('hg')
805 805 is_svn = _RepoChecker('svn')
806 806
807 807
808 808 def get_repo_type_by_name(repo_name):
809 809 repo = Repository.get_by_repo_name(repo_name)
810 810 return repo.repo_type
811 811
812 812
813 813 def is_svn_without_proxy(repository):
814 814 if is_svn(repository):
815 815 from rhodecode.model.settings import VcsSettingsModel
816 816 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
817 817 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
818 818 return False
819 819
820 820
821 821 def discover_user(author):
822 822 """
823 823 Tries to discover RhodeCode User based on the autho string. Author string
824 824 is typically `FirstName LastName <email@address.com>`
825 825 """
826 826
827 827 # if author is already an instance use it for extraction
828 828 if isinstance(author, User):
829 829 return author
830 830
831 831 # Valid email in the attribute passed, see if they're in the system
832 832 _email = author_email(author)
833 833 if _email != '':
834 834 user = User.get_by_email(_email, case_insensitive=True, cache=True)
835 835 if user is not None:
836 836 return user
837 837
838 838 # Maybe it's a username, we try to extract it and fetch by username ?
839 839 _author = author_name(author)
840 840 user = User.get_by_username(_author, case_insensitive=True, cache=True)
841 841 if user is not None:
842 842 return user
843 843
844 844 return None
845 845
846 846
847 847 def email_or_none(author):
848 848 # extract email from the commit string
849 849 _email = author_email(author)
850 850
851 851 # If we have an email, use it, otherwise
852 852 # see if it contains a username we can get an email from
853 853 if _email != '':
854 854 return _email
855 855 else:
856 856 user = User.get_by_username(
857 857 author_name(author), case_insensitive=True, cache=True)
858 858
859 859 if user is not None:
860 860 return user.email
861 861
862 862 # No valid email, not a valid user in the system, none!
863 863 return None
864 864
865 865
866 866 def link_to_user(author, length=0, **kwargs):
867 867 user = discover_user(author)
868 868 # user can be None, but if we have it already it means we can re-use it
869 869 # in the person() function, so we save 1 intensive-query
870 870 if user:
871 871 author = user
872 872
873 873 display_person = person(author, 'username_or_name_or_email')
874 874 if length:
875 875 display_person = shorter(display_person, length)
876 876
877 877 if user:
878 878 return link_to(
879 879 escape(display_person),
880 880 route_path('user_profile', username=user.username),
881 881 **kwargs)
882 882 else:
883 883 return escape(display_person)
884 884
885 885
886 886 def person(author, show_attr="username_and_name"):
887 887 user = discover_user(author)
888 888 if user:
889 889 return getattr(user, show_attr)
890 890 else:
891 891 _author = author_name(author)
892 892 _email = email(author)
893 893 return _author or _email
894 894
895 895
896 896 def author_string(email):
897 897 if email:
898 898 user = User.get_by_email(email, case_insensitive=True, cache=True)
899 899 if user:
900 900 if user.first_name or user.last_name:
901 901 return '%s %s &lt;%s&gt;' % (
902 902 user.first_name, user.last_name, email)
903 903 else:
904 904 return email
905 905 else:
906 906 return email
907 907 else:
908 908 return None
909 909
910 910
911 911 def person_by_id(id_, show_attr="username_and_name"):
912 912 # attr to return from fetched user
913 913 person_getter = lambda usr: getattr(usr, show_attr)
914 914
915 915 #maybe it's an ID ?
916 916 if str(id_).isdigit() or isinstance(id_, int):
917 917 id_ = int(id_)
918 918 user = User.get(id_)
919 919 if user is not None:
920 920 return person_getter(user)
921 921 return id_
922 922
923 923
924 924 def gravatar_with_user(author, show_disabled=False):
925 925 from rhodecode.lib.utils import PartialRenderer
926 926 _render = PartialRenderer('base/base.mako')
927 927 return _render('gravatar_with_user', author, show_disabled=show_disabled)
928 928
929 929
930 930 def desc_stylize(value):
931 931 """
932 932 converts tags from value into html equivalent
933 933
934 934 :param value:
935 935 """
936 936 if not value:
937 937 return ''
938 938
939 939 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
940 940 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
941 941 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
942 942 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
943 943 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
944 944 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
945 945 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
946 946 '<div class="metatag" tag="lang">\\2</div>', value)
947 947 value = re.sub(r'\[([a-z]+)\]',
948 948 '<div class="metatag" tag="\\1">\\1</div>', value)
949 949
950 950 return value
951 951
952 952
953 953 def escaped_stylize(value):
954 954 """
955 955 converts tags from value into html equivalent, but escaping its value first
956 956 """
957 957 if not value:
958 958 return ''
959 959
960 960 # Using default webhelper escape method, but has to force it as a
961 961 # plain unicode instead of a markup tag to be used in regex expressions
962 962 value = unicode(escape(safe_unicode(value)))
963 963
964 964 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
965 965 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
966 966 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
967 967 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
968 968 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
969 969 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
970 970 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
971 971 '<div class="metatag" tag="lang">\\2</div>', value)
972 972 value = re.sub(r'\[([a-z]+)\]',
973 973 '<div class="metatag" tag="\\1">\\1</div>', value)
974 974
975 975 return value
976 976
977 977
978 978 def bool2icon(value):
979 979 """
980 980 Returns boolean value of a given value, represented as html element with
981 981 classes that will represent icons
982 982
983 983 :param value: given value to convert to html node
984 984 """
985 985
986 986 if value: # does bool conversion
987 987 return HTML.tag('i', class_="icon-true")
988 988 else: # not true as bool
989 989 return HTML.tag('i', class_="icon-false")
990 990
991 991
992 992 #==============================================================================
993 993 # PERMS
994 994 #==============================================================================
995 995 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
996 996 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
997 997 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
998 998 csrf_token_key
999 999
1000 1000
1001 1001 #==============================================================================
1002 1002 # GRAVATAR URL
1003 1003 #==============================================================================
1004 1004 class InitialsGravatar(object):
1005 1005 def __init__(self, email_address, first_name, last_name, size=30,
1006 1006 background=None, text_color='#fff'):
1007 1007 self.size = size
1008 1008 self.first_name = first_name
1009 1009 self.last_name = last_name
1010 1010 self.email_address = email_address
1011 1011 self.background = background or self.str2color(email_address)
1012 1012 self.text_color = text_color
1013 1013
1014 1014 def get_color_bank(self):
1015 1015 """
1016 1016 returns a predefined list of colors that gravatars can use.
1017 1017 Those are randomized distinct colors that guarantee readability and
1018 1018 uniqueness.
1019 1019
1020 1020 generated with: http://phrogz.net/css/distinct-colors.html
1021 1021 """
1022 1022 return [
1023 1023 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1024 1024 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1025 1025 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1026 1026 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1027 1027 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1028 1028 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1029 1029 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1030 1030 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1031 1031 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1032 1032 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1033 1033 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1034 1034 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1035 1035 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1036 1036 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1037 1037 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1038 1038 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1039 1039 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1040 1040 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1041 1041 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1042 1042 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1043 1043 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1044 1044 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1045 1045 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1046 1046 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1047 1047 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1048 1048 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1049 1049 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1050 1050 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1051 1051 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1052 1052 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1053 1053 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1054 1054 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1055 1055 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1056 1056 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1057 1057 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1058 1058 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1059 1059 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1060 1060 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1061 1061 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1062 1062 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1063 1063 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1064 1064 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1065 1065 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1066 1066 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1067 1067 '#4f8c46', '#368dd9', '#5c0073'
1068 1068 ]
1069 1069
1070 1070 def rgb_to_hex_color(self, rgb_tuple):
1071 1071 """
1072 1072 Converts an rgb_tuple passed to an hex color.
1073 1073
1074 1074 :param rgb_tuple: tuple with 3 ints represents rgb color space
1075 1075 """
1076 1076 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1077 1077
1078 1078 def email_to_int_list(self, email_str):
1079 1079 """
1080 1080 Get every byte of the hex digest value of email and turn it to integer.
1081 1081 It's going to be always between 0-255
1082 1082 """
1083 1083 digest = md5_safe(email_str.lower())
1084 1084 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1085 1085
1086 1086 def pick_color_bank_index(self, email_str, color_bank):
1087 1087 return self.email_to_int_list(email_str)[0] % len(color_bank)
1088 1088
1089 1089 def str2color(self, email_str):
1090 1090 """
1091 1091 Tries to map in a stable algorithm an email to color
1092 1092
1093 1093 :param email_str:
1094 1094 """
1095 1095 color_bank = self.get_color_bank()
1096 1096 # pick position (module it's length so we always find it in the
1097 1097 # bank even if it's smaller than 256 values
1098 1098 pos = self.pick_color_bank_index(email_str, color_bank)
1099 1099 return color_bank[pos]
1100 1100
1101 1101 def normalize_email(self, email_address):
1102 1102 import unicodedata
1103 1103 # default host used to fill in the fake/missing email
1104 1104 default_host = u'localhost'
1105 1105
1106 1106 if not email_address:
1107 1107 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1108 1108
1109 1109 email_address = safe_unicode(email_address)
1110 1110
1111 1111 if u'@' not in email_address:
1112 1112 email_address = u'%s@%s' % (email_address, default_host)
1113 1113
1114 1114 if email_address.endswith(u'@'):
1115 1115 email_address = u'%s%s' % (email_address, default_host)
1116 1116
1117 1117 email_address = unicodedata.normalize('NFKD', email_address)\
1118 1118 .encode('ascii', 'ignore')
1119 1119 return email_address
1120 1120
1121 1121 def get_initials(self):
1122 1122 """
1123 1123 Returns 2 letter initials calculated based on the input.
1124 1124 The algorithm picks first given email address, and takes first letter
1125 1125 of part before @, and then the first letter of server name. In case
1126 1126 the part before @ is in a format of `somestring.somestring2` it replaces
1127 1127 the server letter with first letter of somestring2
1128 1128
1129 1129 In case function was initialized with both first and lastname, this
1130 1130 overrides the extraction from email by first letter of the first and
1131 1131 last name. We add special logic to that functionality, In case Full name
1132 1132 is compound, like Guido Von Rossum, we use last part of the last name
1133 1133 (Von Rossum) picking `R`.
1134 1134
1135 1135 Function also normalizes the non-ascii characters to they ascii
1136 1136 representation, eg Δ„ => A
1137 1137 """
1138 1138 import unicodedata
1139 1139 # replace non-ascii to ascii
1140 1140 first_name = unicodedata.normalize(
1141 1141 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1142 1142 last_name = unicodedata.normalize(
1143 1143 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1144 1144
1145 1145 # do NFKD encoding, and also make sure email has proper format
1146 1146 email_address = self.normalize_email(self.email_address)
1147 1147
1148 1148 # first push the email initials
1149 1149 prefix, server = email_address.split('@', 1)
1150 1150
1151 1151 # check if prefix is maybe a 'first_name.last_name' syntax
1152 1152 _dot_split = prefix.rsplit('.', 1)
1153 1153 if len(_dot_split) == 2:
1154 1154 initials = [_dot_split[0][0], _dot_split[1][0]]
1155 1155 else:
1156 1156 initials = [prefix[0], server[0]]
1157 1157
1158 1158 # then try to replace either first_name or last_name
1159 1159 fn_letter = (first_name or " ")[0].strip()
1160 1160 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1161 1161
1162 1162 if fn_letter:
1163 1163 initials[0] = fn_letter
1164 1164
1165 1165 if ln_letter:
1166 1166 initials[1] = ln_letter
1167 1167
1168 1168 return ''.join(initials).upper()
1169 1169
1170 1170 def get_img_data_by_type(self, font_family, img_type):
1171 1171 default_user = """
1172 1172 <svg xmlns="http://www.w3.org/2000/svg"
1173 1173 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1174 1174 viewBox="-15 -10 439.165 429.164"
1175 1175
1176 1176 xml:space="preserve"
1177 1177 style="background:{background};" >
1178 1178
1179 1179 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1180 1180 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1181 1181 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1182 1182 168.596,153.916,216.671,
1183 1183 204.583,216.671z" fill="{text_color}"/>
1184 1184 <path d="M407.164,374.717L360.88,
1185 1185 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1186 1186 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1187 1187 15.366-44.203,23.488-69.076,23.488c-24.877,
1188 1188 0-48.762-8.122-69.078-23.488
1189 1189 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1190 1190 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1191 1191 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1192 1192 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1193 1193 19.402-10.527 C409.699,390.129,
1194 1194 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1195 1195 </svg>""".format(
1196 1196 size=self.size,
1197 1197 background='#979797', # @grey4
1198 1198 text_color=self.text_color,
1199 1199 font_family=font_family)
1200 1200
1201 1201 return {
1202 1202 "default_user": default_user
1203 1203 }[img_type]
1204 1204
1205 1205 def get_img_data(self, svg_type=None):
1206 1206 """
1207 1207 generates the svg metadata for image
1208 1208 """
1209 1209
1210 1210 font_family = ','.join([
1211 1211 'proximanovaregular',
1212 1212 'Proxima Nova Regular',
1213 1213 'Proxima Nova',
1214 1214 'Arial',
1215 1215 'Lucida Grande',
1216 1216 'sans-serif'
1217 1217 ])
1218 1218 if svg_type:
1219 1219 return self.get_img_data_by_type(font_family, svg_type)
1220 1220
1221 1221 initials = self.get_initials()
1222 1222 img_data = """
1223 1223 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1224 1224 width="{size}" height="{size}"
1225 1225 style="width: 100%; height: 100%; background-color: {background}"
1226 1226 viewBox="0 0 {size} {size}">
1227 1227 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1228 1228 pointer-events="auto" fill="{text_color}"
1229 1229 font-family="{font_family}"
1230 1230 style="font-weight: 400; font-size: {f_size}px;">{text}
1231 1231 </text>
1232 1232 </svg>""".format(
1233 1233 size=self.size,
1234 1234 f_size=self.size/1.85, # scale the text inside the box nicely
1235 1235 background=self.background,
1236 1236 text_color=self.text_color,
1237 1237 text=initials.upper(),
1238 1238 font_family=font_family)
1239 1239
1240 1240 return img_data
1241 1241
1242 1242 def generate_svg(self, svg_type=None):
1243 1243 img_data = self.get_img_data(svg_type)
1244 1244 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1245 1245
1246 1246
1247 1247 def initials_gravatar(email_address, first_name, last_name, size=30):
1248 1248 svg_type = None
1249 1249 if email_address == User.DEFAULT_USER_EMAIL:
1250 1250 svg_type = 'default_user'
1251 1251 klass = InitialsGravatar(email_address, first_name, last_name, size)
1252 1252 return klass.generate_svg(svg_type=svg_type)
1253 1253
1254 1254
1255 1255 def gravatar_url(email_address, size=30, request=None):
1256 1256 request = get_current_request()
1257 1257 if request and hasattr(request, 'call_context'):
1258 1258 _use_gravatar = request.call_context.visual.use_gravatar
1259 1259 _gravatar_url = request.call_context.visual.gravatar_url
1260 1260 else:
1261 1261 # doh, we need to re-import those to mock it later
1262 1262 from pylons import tmpl_context as c
1263 1263
1264 1264 _use_gravatar = c.visual.use_gravatar
1265 1265 _gravatar_url = c.visual.gravatar_url
1266 1266
1267 1267 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1268 1268
1269 1269 email_address = email_address or User.DEFAULT_USER_EMAIL
1270 1270 if isinstance(email_address, unicode):
1271 1271 # hashlib crashes on unicode items
1272 1272 email_address = safe_str(email_address)
1273 1273
1274 1274 # empty email or default user
1275 1275 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1276 1276 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1277 1277
1278 1278 if _use_gravatar:
1279 1279 # TODO: Disuse pyramid thread locals. Think about another solution to
1280 1280 # get the host and schema here.
1281 1281 request = get_current_request()
1282 1282 tmpl = safe_str(_gravatar_url)
1283 1283 tmpl = tmpl.replace('{email}', email_address)\
1284 1284 .replace('{md5email}', md5_safe(email_address.lower())) \
1285 1285 .replace('{netloc}', request.host)\
1286 1286 .replace('{scheme}', request.scheme)\
1287 1287 .replace('{size}', safe_str(size))
1288 1288 return tmpl
1289 1289 else:
1290 1290 return initials_gravatar(email_address, '', '', size=size)
1291 1291
1292 1292
1293 1293 class Page(_Page):
1294 1294 """
1295 1295 Custom pager to match rendering style with paginator
1296 1296 """
1297 1297
1298 1298 def _get_pos(self, cur_page, max_page, items):
1299 1299 edge = (items / 2) + 1
1300 1300 if (cur_page <= edge):
1301 1301 radius = max(items / 2, items - cur_page)
1302 1302 elif (max_page - cur_page) < edge:
1303 1303 radius = (items - 1) - (max_page - cur_page)
1304 1304 else:
1305 1305 radius = items / 2
1306 1306
1307 1307 left = max(1, (cur_page - (radius)))
1308 1308 right = min(max_page, cur_page + (radius))
1309 1309 return left, cur_page, right
1310 1310
1311 1311 def _range(self, regexp_match):
1312 1312 """
1313 1313 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1314 1314
1315 1315 Arguments:
1316 1316
1317 1317 regexp_match
1318 1318 A "re" (regular expressions) match object containing the
1319 1319 radius of linked pages around the current page in
1320 1320 regexp_match.group(1) as a string
1321 1321
1322 1322 This function is supposed to be called as a callable in
1323 1323 re.sub.
1324 1324
1325 1325 """
1326 1326 radius = int(regexp_match.group(1))
1327 1327
1328 1328 # Compute the first and last page number within the radius
1329 1329 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1330 1330 # -> leftmost_page = 5
1331 1331 # -> rightmost_page = 9
1332 1332 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1333 1333 self.last_page,
1334 1334 (radius * 2) + 1)
1335 1335 nav_items = []
1336 1336
1337 1337 # Create a link to the first page (unless we are on the first page
1338 1338 # or there would be no need to insert '..' spacers)
1339 1339 if self.page != self.first_page and self.first_page < leftmost_page:
1340 1340 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1341 1341
1342 1342 # Insert dots if there are pages between the first page
1343 1343 # and the currently displayed page range
1344 1344 if leftmost_page - self.first_page > 1:
1345 1345 # Wrap in a SPAN tag if nolink_attr is set
1346 1346 text = '..'
1347 1347 if self.dotdot_attr:
1348 1348 text = HTML.span(c=text, **self.dotdot_attr)
1349 1349 nav_items.append(text)
1350 1350
1351 1351 for thispage in xrange(leftmost_page, rightmost_page + 1):
1352 1352 # Hilight the current page number and do not use a link
1353 1353 if thispage == self.page:
1354 1354 text = '%s' % (thispage,)
1355 1355 # Wrap in a SPAN tag if nolink_attr is set
1356 1356 if self.curpage_attr:
1357 1357 text = HTML.span(c=text, **self.curpage_attr)
1358 1358 nav_items.append(text)
1359 1359 # Otherwise create just a link to that page
1360 1360 else:
1361 1361 text = '%s' % (thispage,)
1362 1362 nav_items.append(self._pagerlink(thispage, text))
1363 1363
1364 1364 # Insert dots if there are pages between the displayed
1365 1365 # page numbers and the end of the page range
1366 1366 if self.last_page - rightmost_page > 1:
1367 1367 text = '..'
1368 1368 # Wrap in a SPAN tag if nolink_attr is set
1369 1369 if self.dotdot_attr:
1370 1370 text = HTML.span(c=text, **self.dotdot_attr)
1371 1371 nav_items.append(text)
1372 1372
1373 1373 # Create a link to the very last page (unless we are on the last
1374 1374 # page or there would be no need to insert '..' spacers)
1375 1375 if self.page != self.last_page and rightmost_page < self.last_page:
1376 1376 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1377 1377
1378 1378 ## prerender links
1379 1379 #_page_link = url.current()
1380 1380 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1381 1381 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1382 1382 return self.separator.join(nav_items)
1383 1383
1384 1384 def pager(self, format='~2~', page_param='page', partial_param='partial',
1385 1385 show_if_single_page=False, separator=' ', onclick=None,
1386 1386 symbol_first='<<', symbol_last='>>',
1387 1387 symbol_previous='<', symbol_next='>',
1388 1388 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1389 1389 curpage_attr={'class': 'pager_curpage'},
1390 1390 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1391 1391
1392 1392 self.curpage_attr = curpage_attr
1393 1393 self.separator = separator
1394 1394 self.pager_kwargs = kwargs
1395 1395 self.page_param = page_param
1396 1396 self.partial_param = partial_param
1397 1397 self.onclick = onclick
1398 1398 self.link_attr = link_attr
1399 1399 self.dotdot_attr = dotdot_attr
1400 1400
1401 1401 # Don't show navigator if there is no more than one page
1402 1402 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1403 1403 return ''
1404 1404
1405 1405 from string import Template
1406 1406 # Replace ~...~ in token format by range of pages
1407 1407 result = re.sub(r'~(\d+)~', self._range, format)
1408 1408
1409 1409 # Interpolate '%' variables
1410 1410 result = Template(result).safe_substitute({
1411 1411 'first_page': self.first_page,
1412 1412 'last_page': self.last_page,
1413 1413 'page': self.page,
1414 1414 'page_count': self.page_count,
1415 1415 'items_per_page': self.items_per_page,
1416 1416 'first_item': self.first_item,
1417 1417 'last_item': self.last_item,
1418 1418 'item_count': self.item_count,
1419 1419 'link_first': self.page > self.first_page and \
1420 1420 self._pagerlink(self.first_page, symbol_first) or '',
1421 1421 'link_last': self.page < self.last_page and \
1422 1422 self._pagerlink(self.last_page, symbol_last) or '',
1423 1423 'link_previous': self.previous_page and \
1424 1424 self._pagerlink(self.previous_page, symbol_previous) \
1425 1425 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1426 1426 'link_next': self.next_page and \
1427 1427 self._pagerlink(self.next_page, symbol_next) \
1428 1428 or HTML.span(symbol_next, class_="pg-next disabled")
1429 1429 })
1430 1430
1431 1431 return literal(result)
1432 1432
1433 1433
1434 1434 #==============================================================================
1435 1435 # REPO PAGER, PAGER FOR REPOSITORY
1436 1436 #==============================================================================
1437 1437 class RepoPage(Page):
1438 1438
1439 1439 def __init__(self, collection, page=1, items_per_page=20,
1440 1440 item_count=None, url=None, **kwargs):
1441 1441
1442 1442 """Create a "RepoPage" instance. special pager for paging
1443 1443 repository
1444 1444 """
1445 1445 self._url_generator = url
1446 1446
1447 1447 # Safe the kwargs class-wide so they can be used in the pager() method
1448 1448 self.kwargs = kwargs
1449 1449
1450 1450 # Save a reference to the collection
1451 1451 self.original_collection = collection
1452 1452
1453 1453 self.collection = collection
1454 1454
1455 1455 # The self.page is the number of the current page.
1456 1456 # The first page has the number 1!
1457 1457 try:
1458 1458 self.page = int(page) # make it int() if we get it as a string
1459 1459 except (ValueError, TypeError):
1460 1460 self.page = 1
1461 1461
1462 1462 self.items_per_page = items_per_page
1463 1463
1464 1464 # Unless the user tells us how many items the collections has
1465 1465 # we calculate that ourselves.
1466 1466 if item_count is not None:
1467 1467 self.item_count = item_count
1468 1468 else:
1469 1469 self.item_count = len(self.collection)
1470 1470
1471 1471 # Compute the number of the first and last available page
1472 1472 if self.item_count > 0:
1473 1473 self.first_page = 1
1474 1474 self.page_count = int(math.ceil(float(self.item_count) /
1475 1475 self.items_per_page))
1476 1476 self.last_page = self.first_page + self.page_count - 1
1477 1477
1478 1478 # Make sure that the requested page number is the range of
1479 1479 # valid pages
1480 1480 if self.page > self.last_page:
1481 1481 self.page = self.last_page
1482 1482 elif self.page < self.first_page:
1483 1483 self.page = self.first_page
1484 1484
1485 1485 # Note: the number of items on this page can be less than
1486 1486 # items_per_page if the last page is not full
1487 1487 self.first_item = max(0, (self.item_count) - (self.page *
1488 1488 items_per_page))
1489 1489 self.last_item = ((self.item_count - 1) - items_per_page *
1490 1490 (self.page - 1))
1491 1491
1492 1492 self.items = list(self.collection[self.first_item:self.last_item + 1])
1493 1493
1494 1494 # Links to previous and next page
1495 1495 if self.page > self.first_page:
1496 1496 self.previous_page = self.page - 1
1497 1497 else:
1498 1498 self.previous_page = None
1499 1499
1500 1500 if self.page < self.last_page:
1501 1501 self.next_page = self.page + 1
1502 1502 else:
1503 1503 self.next_page = None
1504 1504
1505 1505 # No items available
1506 1506 else:
1507 1507 self.first_page = None
1508 1508 self.page_count = 0
1509 1509 self.last_page = None
1510 1510 self.first_item = None
1511 1511 self.last_item = None
1512 1512 self.previous_page = None
1513 1513 self.next_page = None
1514 1514 self.items = []
1515 1515
1516 1516 # This is a subclass of the 'list' type. Initialise the list now.
1517 1517 list.__init__(self, reversed(self.items))
1518 1518
1519 1519
1520 1520 def breadcrumb_repo_link(repo):
1521 1521 """
1522 1522 Makes a breadcrumbs path link to repo
1523 1523
1524 1524 ex::
1525 1525 group >> subgroup >> repo
1526 1526
1527 1527 :param repo: a Repository instance
1528 1528 """
1529 1529
1530 1530 path = [
1531 1531 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1532 1532 for group in repo.groups_with_parents
1533 1533 ] + [
1534 1534 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1535 1535 ]
1536 1536
1537 1537 return literal(' &raquo; '.join(path))
1538 1538
1539 1539
1540 1540 def format_byte_size_binary(file_size):
1541 1541 """
1542 1542 Formats file/folder sizes to standard.
1543 1543 """
1544 1544 formatted_size = format_byte_size(file_size, binary=True)
1545 1545 return formatted_size
1546 1546
1547 1547
1548 1548 def urlify_text(text_, safe=True):
1549 1549 """
1550 1550 Extrac urls from text and make html links out of them
1551 1551
1552 1552 :param text_:
1553 1553 """
1554 1554
1555 1555 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1556 1556 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1557 1557
1558 1558 def url_func(match_obj):
1559 1559 url_full = match_obj.groups()[0]
1560 1560 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1561 1561 _newtext = url_pat.sub(url_func, text_)
1562 1562 if safe:
1563 1563 return literal(_newtext)
1564 1564 return _newtext
1565 1565
1566 1566
1567 1567 def urlify_commits(text_, repository):
1568 1568 """
1569 1569 Extract commit ids from text and make link from them
1570 1570
1571 1571 :param text_:
1572 1572 :param repository: repo name to build the URL with
1573 1573 """
1574 1574 from pylons import url # doh, we need to re-import url to mock it later
1575 1575 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1576 1576
1577 1577 def url_func(match_obj):
1578 1578 commit_id = match_obj.groups()[1]
1579 1579 pref = match_obj.groups()[0]
1580 1580 suf = match_obj.groups()[2]
1581 1581
1582 1582 tmpl = (
1583 1583 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1584 1584 '%(commit_id)s</a>%(suf)s'
1585 1585 )
1586 1586 return tmpl % {
1587 1587 'pref': pref,
1588 1588 'cls': 'revision-link',
1589 1589 'url': url('changeset_home', repo_name=repository,
1590 1590 revision=commit_id, qualified=True),
1591 1591 'commit_id': commit_id,
1592 1592 'suf': suf
1593 1593 }
1594 1594
1595 1595 newtext = URL_PAT.sub(url_func, text_)
1596 1596
1597 1597 return newtext
1598 1598
1599 1599
1600 1600 def _process_url_func(match_obj, repo_name, uid, entry,
1601 1601 return_raw_data=False, link_format='html'):
1602 1602 pref = ''
1603 1603 if match_obj.group().startswith(' '):
1604 1604 pref = ' '
1605 1605
1606 1606 issue_id = ''.join(match_obj.groups())
1607 1607
1608 1608 if link_format == 'html':
1609 1609 tmpl = (
1610 1610 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1611 1611 '%(issue-prefix)s%(id-repr)s'
1612 1612 '</a>')
1613 1613 elif link_format == 'rst':
1614 1614 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1615 1615 elif link_format == 'markdown':
1616 1616 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1617 1617 else:
1618 1618 raise ValueError('Bad link_format:{}'.format(link_format))
1619 1619
1620 1620 (repo_name_cleaned,
1621 1621 parent_group_name) = RepoGroupModel().\
1622 1622 _get_group_name_and_parent(repo_name)
1623 1623
1624 1624 # variables replacement
1625 1625 named_vars = {
1626 1626 'id': issue_id,
1627 1627 'repo': repo_name,
1628 1628 'repo_name': repo_name_cleaned,
1629 1629 'group_name': parent_group_name
1630 1630 }
1631 1631 # named regex variables
1632 1632 named_vars.update(match_obj.groupdict())
1633 1633 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1634 1634
1635 1635 data = {
1636 1636 'pref': pref,
1637 1637 'cls': 'issue-tracker-link',
1638 1638 'url': _url,
1639 1639 'id-repr': issue_id,
1640 1640 'issue-prefix': entry['pref'],
1641 1641 'serv': entry['url'],
1642 1642 }
1643 1643 if return_raw_data:
1644 1644 return {
1645 1645 'id': issue_id,
1646 1646 'url': _url
1647 1647 }
1648 1648 return tmpl % data
1649 1649
1650 1650
1651 1651 def process_patterns(text_string, repo_name, link_format='html'):
1652 1652 allowed_formats = ['html', 'rst', 'markdown']
1653 1653 if link_format not in allowed_formats:
1654 1654 raise ValueError('Link format can be only one of:{} got {}'.format(
1655 1655 allowed_formats, link_format))
1656 1656
1657 1657 repo = None
1658 1658 if repo_name:
1659 1659 # Retrieving repo_name to avoid invalid repo_name to explode on
1660 1660 # IssueTrackerSettingsModel but still passing invalid name further down
1661 1661 repo = Repository.get_by_repo_name(repo_name, cache=True)
1662 1662
1663 1663 settings_model = IssueTrackerSettingsModel(repo=repo)
1664 1664 active_entries = settings_model.get_settings(cache=True)
1665 1665
1666 1666 issues_data = []
1667 1667 newtext = text_string
1668 1668
1669 1669 for uid, entry in active_entries.items():
1670 1670 log.debug('found issue tracker entry with uid %s' % (uid,))
1671 1671
1672 1672 if not (entry['pat'] and entry['url']):
1673 1673 log.debug('skipping due to missing data')
1674 1674 continue
1675 1675
1676 1676 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1677 1677 % (uid, entry['pat'], entry['url'], entry['pref']))
1678 1678
1679 1679 try:
1680 1680 pattern = re.compile(r'%s' % entry['pat'])
1681 1681 except re.error:
1682 1682 log.exception(
1683 1683 'issue tracker pattern: `%s` failed to compile',
1684 1684 entry['pat'])
1685 1685 continue
1686 1686
1687 1687 data_func = partial(
1688 1688 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1689 1689 return_raw_data=True)
1690 1690
1691 1691 for match_obj in pattern.finditer(text_string):
1692 1692 issues_data.append(data_func(match_obj))
1693 1693
1694 1694 url_func = partial(
1695 1695 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1696 1696 link_format=link_format)
1697 1697
1698 1698 newtext = pattern.sub(url_func, newtext)
1699 1699 log.debug('processed prefix:uid `%s`' % (uid,))
1700 1700
1701 1701 return newtext, issues_data
1702 1702
1703 1703
1704 1704 def urlify_commit_message(commit_text, repository=None):
1705 1705 """
1706 1706 Parses given text message and makes proper links.
1707 1707 issues are linked to given issue-server, and rest is a commit link
1708 1708
1709 1709 :param commit_text:
1710 1710 :param repository:
1711 1711 """
1712 1712 from pylons import url # doh, we need to re-import url to mock it later
1713 1713
1714 1714 def escaper(string):
1715 1715 return string.replace('<', '&lt;').replace('>', '&gt;')
1716 1716
1717 1717 newtext = escaper(commit_text)
1718 1718
1719 1719 # extract http/https links and make them real urls
1720 1720 newtext = urlify_text(newtext, safe=False)
1721 1721
1722 1722 # urlify commits - extract commit ids and make link out of them, if we have
1723 1723 # the scope of repository present.
1724 1724 if repository:
1725 1725 newtext = urlify_commits(newtext, repository)
1726 1726
1727 1727 # process issue tracker patterns
1728 1728 newtext, issues = process_patterns(newtext, repository or '')
1729 1729
1730 1730 return literal(newtext)
1731 1731
1732 1732
1733 1733 def render_binary(repo_name, file_obj):
1734 1734 """
1735 1735 Choose how to render a binary file
1736 1736 """
1737 1737 filename = file_obj.name
1738 1738
1739 1739 # images
1740 1740 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1741 1741 if fnmatch.fnmatch(filename, pat=ext):
1742 1742 alt = filename
1743 1743 src = url('files_raw_home', repo_name=repo_name,
1744 1744 revision=file_obj.commit.raw_id, f_path=file_obj.path)
1745 1745 return literal('<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1746 1746
1747 1747
1748 1748 def renderer_from_filename(filename, exclude=None):
1749 1749 """
1750 1750 choose a renderer based on filename, this works only for text based files
1751 1751 """
1752 1752
1753 1753 # ipython
1754 1754 for ext in ['*.ipynb']:
1755 1755 if fnmatch.fnmatch(filename, pat=ext):
1756 1756 return 'jupyter'
1757 1757
1758 1758 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1759 1759 if is_markup:
1760 1760 return is_markup
1761 1761 return None
1762 1762
1763 1763
1764 1764 def render(source, renderer='rst', mentions=False, relative_url=None,
1765 1765 repo_name=None):
1766 1766
1767 1767 def maybe_convert_relative_links(html_source):
1768 1768 if relative_url:
1769 1769 return relative_links(html_source, relative_url)
1770 1770 return html_source
1771 1771
1772 1772 if renderer == 'rst':
1773 1773 if repo_name:
1774 1774 # process patterns on comments if we pass in repo name
1775 1775 source, issues = process_patterns(
1776 1776 source, repo_name, link_format='rst')
1777 1777
1778 1778 return literal(
1779 1779 '<div class="rst-block">%s</div>' %
1780 1780 maybe_convert_relative_links(
1781 1781 MarkupRenderer.rst(source, mentions=mentions)))
1782 1782 elif renderer == 'markdown':
1783 1783 if repo_name:
1784 1784 # process patterns on comments if we pass in repo name
1785 1785 source, issues = process_patterns(
1786 1786 source, repo_name, link_format='markdown')
1787 1787
1788 1788 return literal(
1789 1789 '<div class="markdown-block">%s</div>' %
1790 1790 maybe_convert_relative_links(
1791 1791 MarkupRenderer.markdown(source, flavored=True,
1792 1792 mentions=mentions)))
1793 1793 elif renderer == 'jupyter':
1794 1794 return literal(
1795 1795 '<div class="ipynb">%s</div>' %
1796 1796 maybe_convert_relative_links(
1797 1797 MarkupRenderer.jupyter(source)))
1798 1798
1799 1799 # None means just show the file-source
1800 1800 return None
1801 1801
1802 1802
1803 1803 def commit_status(repo, commit_id):
1804 1804 return ChangesetStatusModel().get_status(repo, commit_id)
1805 1805
1806 1806
1807 1807 def commit_status_lbl(commit_status):
1808 1808 return dict(ChangesetStatus.STATUSES).get(commit_status)
1809 1809
1810 1810
1811 1811 def commit_time(repo_name, commit_id):
1812 1812 repo = Repository.get_by_repo_name(repo_name)
1813 1813 commit = repo.get_commit(commit_id=commit_id)
1814 1814 return commit.date
1815 1815
1816 1816
1817 1817 def get_permission_name(key):
1818 1818 return dict(Permission.PERMS).get(key)
1819 1819
1820 1820
1821 1821 def journal_filter_help(request):
1822 1822 _ = request.translate
1823 1823
1824 1824 return _(
1825 1825 'Example filter terms:\n' +
1826 1826 ' repository:vcs\n' +
1827 1827 ' username:marcin\n' +
1828 1828 ' username:(NOT marcin)\n' +
1829 1829 ' action:*push*\n' +
1830 1830 ' ip:127.0.0.1\n' +
1831 1831 ' date:20120101\n' +
1832 1832 ' date:[20120101100000 TO 20120102]\n' +
1833 1833 '\n' +
1834 1834 'Generate wildcards using \'*\' character:\n' +
1835 1835 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1836 1836 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1837 1837 '\n' +
1838 1838 'Optional AND / OR operators in queries\n' +
1839 1839 ' "repository:vcs OR repository:test"\n' +
1840 1840 ' "username:test AND repository:test*"\n'
1841 1841 )
1842 1842
1843 1843
1844 1844 def search_filter_help(searcher, request):
1845 1845 _ = request.translate
1846 1846
1847 1847 terms = ''
1848 1848 return _(
1849 1849 'Example filter terms for `{searcher}` search:\n' +
1850 1850 '{terms}\n' +
1851 1851 'Generate wildcards using \'*\' character:\n' +
1852 1852 ' "repo_name:vcs*" - search everything starting with \'vcs\'\n' +
1853 1853 ' "repo_name:*vcs*" - search for repository containing \'vcs\'\n' +
1854 1854 '\n' +
1855 1855 'Optional AND / OR operators in queries\n' +
1856 1856 ' "repo_name:vcs OR repo_name:test"\n' +
1857 1857 ' "owner:test AND repo_name:test*"\n' +
1858 1858 'More: {search_doc}'
1859 1859 ).format(searcher=searcher.name,
1860 1860 terms=terms, search_doc=searcher.query_lang_doc)
1861 1861
1862 1862
1863 1863 def not_mapped_error(repo_name):
1864 1864 from rhodecode.translation import _
1865 1865 flash(_('%s repository is not mapped to db perhaps'
1866 1866 ' it was created or renamed from the filesystem'
1867 1867 ' please run the application again'
1868 1868 ' in order to rescan repositories') % repo_name, category='error')
1869 1869
1870 1870
1871 1871 def ip_range(ip_addr):
1872 1872 from rhodecode.model.db import UserIpMap
1873 1873 s, e = UserIpMap._get_ip_range(ip_addr)
1874 1874 return '%s - %s' % (s, e)
1875 1875
1876 1876
1877 1877 def form(url, method='post', needs_csrf_token=True, **attrs):
1878 1878 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1879 1879 if method.lower() != 'get' and needs_csrf_token:
1880 1880 raise Exception(
1881 1881 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1882 1882 'CSRF token. If the endpoint does not require such token you can ' +
1883 1883 'explicitly set the parameter needs_csrf_token to false.')
1884 1884
1885 1885 return wh_form(url, method=method, **attrs)
1886 1886
1887 1887
1888 1888 def secure_form(url, method="POST", multipart=False, **attrs):
1889 1889 """Start a form tag that points the action to an url. This
1890 1890 form tag will also include the hidden field containing
1891 1891 the auth token.
1892 1892
1893 1893 The url options should be given either as a string, or as a
1894 1894 ``url()`` function. The method for the form defaults to POST.
1895 1895
1896 1896 Options:
1897 1897
1898 1898 ``multipart``
1899 1899 If set to True, the enctype is set to "multipart/form-data".
1900 1900 ``method``
1901 1901 The method to use when submitting the form, usually either
1902 1902 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1903 1903 hidden input with name _method is added to simulate the verb
1904 1904 over POST.
1905 1905
1906 1906 """
1907 1907 from webhelpers.pylonslib.secure_form import insecure_form
1908 1908 form = insecure_form(url, method, multipart, **attrs)
1909 token = csrf_input()
1909
1910 session = None
1911 # TODO(marcink): after pyramid migration require request variable ALWAYS
1912 if 'request' in attrs:
1913 session = attrs['request'].session
1914
1915 token = literal(
1916 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1917 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1918
1910 1919 return literal("%s\n%s" % (form, token))
1911 1920
1912 def csrf_input():
1913 return literal(
1914 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1915 csrf_token_key, csrf_token_key, get_csrf_token()))
1916 1921
1917 1922 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1918 1923 select_html = select(name, selected, options, **attrs)
1919 1924 select2 = """
1920 1925 <script>
1921 1926 $(document).ready(function() {
1922 1927 $('#%s').select2({
1923 1928 containerCssClass: 'drop-menu',
1924 1929 dropdownCssClass: 'drop-menu-dropdown',
1925 1930 dropdownAutoWidth: true%s
1926 1931 });
1927 1932 });
1928 1933 </script>
1929 1934 """
1930 1935 filter_option = """,
1931 1936 minimumResultsForSearch: -1
1932 1937 """
1933 1938 input_id = attrs.get('id') or name
1934 1939 filter_enabled = "" if enable_filter else filter_option
1935 1940 select_script = literal(select2 % (input_id, filter_enabled))
1936 1941
1937 1942 return literal(select_html+select_script)
1938 1943
1939 1944
1940 1945 def get_visual_attr(tmpl_context_var, attr_name):
1941 1946 """
1942 1947 A safe way to get a variable from visual variable of template context
1943 1948
1944 1949 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1945 1950 :param attr_name: name of the attribute we fetch from the c.visual
1946 1951 """
1947 1952 visual = getattr(tmpl_context_var, 'visual', None)
1948 1953 if not visual:
1949 1954 return
1950 1955 else:
1951 1956 return getattr(visual, attr_name, None)
1952 1957
1953 1958
1954 1959 def get_last_path_part(file_node):
1955 1960 if not file_node.path:
1956 1961 return u''
1957 1962
1958 1963 path = safe_unicode(file_node.path.split('/')[-1])
1959 1964 return u'../' + path
1960 1965
1961 1966
1962 1967 def route_url(*args, **kwargs):
1963 1968 """
1964 1969 Wrapper around pyramids `route_url` (fully qualified url) function.
1965 1970 It is used to generate URLs from within pylons views or templates.
1966 1971 This will be removed when pyramid migration if finished.
1967 1972 """
1968 1973 req = get_current_request()
1969 1974 return req.route_url(*args, **kwargs)
1970 1975
1971 1976
1972 1977 def route_path(*args, **kwargs):
1973 1978 """
1974 1979 Wrapper around pyramids `route_path` function. It is used to generate
1975 1980 URLs from within pylons views or templates. This will be removed when
1976 1981 pyramid migration if finished.
1977 1982 """
1978 1983 req = get_current_request()
1979 1984 return req.route_path(*args, **kwargs)
1980 1985
1981 1986
1982 1987 def route_path_or_none(*args, **kwargs):
1983 1988 try:
1984 1989 return route_path(*args, **kwargs)
1985 1990 except KeyError:
1986 1991 return None
1987 1992
1988 1993
1989 1994 def static_url(*args, **kwds):
1990 1995 """
1991 1996 Wrapper around pyramids `route_path` function. It is used to generate
1992 1997 URLs from within pylons views or templates. This will be removed when
1993 1998 pyramid migration if finished.
1994 1999 """
1995 2000 req = get_current_request()
1996 2001 return req.static_url(*args, **kwds)
1997 2002
1998 2003
1999 2004 def resource_path(*args, **kwds):
2000 2005 """
2001 2006 Wrapper around pyramids `route_path` function. It is used to generate
2002 2007 URLs from within pylons views or templates. This will be removed when
2003 2008 pyramid migration if finished.
2004 2009 """
2005 2010 req = get_current_request()
2006 2011 return req.resource_path(*args, **kwds)
2007 2012
2008 2013
2009 2014 def api_call_example(method, args):
2010 2015 """
2011 2016 Generates an API call example via CURL
2012 2017 """
2013 2018 args_json = json.dumps(OrderedDict([
2014 2019 ('id', 1),
2015 2020 ('auth_token', 'SECRET'),
2016 2021 ('method', method),
2017 2022 ('args', args)
2018 2023 ]))
2019 2024 return literal(
2020 2025 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2021 2026 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2022 2027 "and needs to be of `api calls` role."
2023 2028 .format(
2024 2029 api_url=route_url('apiv2'),
2025 2030 token_url=route_url('my_account_auth_tokens'),
2026 2031 data=args_json))
@@ -1,563 +1,565 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 this is forms validation classes
23 23 http://formencode.org/module-formencode.validators.html
24 24 for list off all availible validators
25 25
26 26 we can create our own validators
27 27
28 28 The table below outlines the options which can be used in a schema in addition to the validators themselves
29 29 pre_validators [] These validators will be applied before the schema
30 30 chained_validators [] These validators will be applied after the schema
31 31 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
32 32 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
33 33 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
34 34 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
35 35
36 36
37 37 <name> = formencode.validators.<name of validator>
38 38 <name> must equal form name
39 39 list=[1,2,3,4,5]
40 40 for SELECT use formencode.All(OneOf(list), Int())
41 41
42 42 """
43 43
44 44 import deform
45 45 import logging
46 46 import formencode
47 47
48 48 from pkg_resources import resource_filename
49 49 from formencode import All, Pipe
50 50
51 51 from pylons.i18n.translation import _
52 from pyramid.threadlocal import get_current_request
52 53
53 54 from rhodecode import BACKENDS
54 55 from rhodecode.lib import helpers
55 56 from rhodecode.model import validators as v
56 57
57 58 log = logging.getLogger(__name__)
58 59
59 60
60 61 deform_templates = resource_filename('deform', 'templates')
61 62 rhodecode_templates = resource_filename('rhodecode', 'templates/forms')
62 63 search_path = (rhodecode_templates, deform_templates)
63 64
64 65
65 66 class RhodecodeFormZPTRendererFactory(deform.ZPTRendererFactory):
66 67 """ Subclass of ZPTRendererFactory to add rhodecode context variables """
67 68 def __call__(self, template_name, **kw):
68 69 kw['h'] = helpers
70 kw['request'] = get_current_request()
69 71 return self.load(template_name)(**kw)
70 72
71 73
72 74 form_renderer = RhodecodeFormZPTRendererFactory(search_path)
73 75 deform.Form.set_default_renderer(form_renderer)
74 76
75 77
76 78 def LoginForm():
77 79 class _LoginForm(formencode.Schema):
78 80 allow_extra_fields = True
79 81 filter_extra_fields = True
80 82 username = v.UnicodeString(
81 83 strip=True,
82 84 min=1,
83 85 not_empty=True,
84 86 messages={
85 87 'empty': _(u'Please enter a login'),
86 88 'tooShort': _(u'Enter a value %(min)i characters long or more')
87 89 }
88 90 )
89 91
90 92 password = v.UnicodeString(
91 93 strip=False,
92 94 min=3,
93 95 not_empty=True,
94 96 messages={
95 97 'empty': _(u'Please enter a password'),
96 98 'tooShort': _(u'Enter %(min)i characters or more')}
97 99 )
98 100
99 101 remember = v.StringBoolean(if_missing=False)
100 102
101 103 chained_validators = [v.ValidAuth()]
102 104 return _LoginForm
103 105
104 106
105 107 def UserForm(edit=False, available_languages=[], old_data={}):
106 108 class _UserForm(formencode.Schema):
107 109 allow_extra_fields = True
108 110 filter_extra_fields = True
109 111 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
110 112 v.ValidUsername(edit, old_data))
111 113 if edit:
112 114 new_password = All(
113 115 v.ValidPassword(),
114 116 v.UnicodeString(strip=False, min=6, not_empty=False)
115 117 )
116 118 password_confirmation = All(
117 119 v.ValidPassword(),
118 120 v.UnicodeString(strip=False, min=6, not_empty=False),
119 121 )
120 122 admin = v.StringBoolean(if_missing=False)
121 123 else:
122 124 password = All(
123 125 v.ValidPassword(),
124 126 v.UnicodeString(strip=False, min=6, not_empty=True)
125 127 )
126 128 password_confirmation = All(
127 129 v.ValidPassword(),
128 130 v.UnicodeString(strip=False, min=6, not_empty=False)
129 131 )
130 132
131 133 password_change = v.StringBoolean(if_missing=False)
132 134 create_repo_group = v.StringBoolean(if_missing=False)
133 135
134 136 active = v.StringBoolean(if_missing=False)
135 137 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
136 138 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
137 139 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
138 140 extern_name = v.UnicodeString(strip=True)
139 141 extern_type = v.UnicodeString(strip=True)
140 142 language = v.OneOf(available_languages, hideList=False,
141 143 testValueList=True, if_missing=None)
142 144 chained_validators = [v.ValidPasswordsMatch()]
143 145 return _UserForm
144 146
145 147
146 148 def UserGroupForm(edit=False, old_data=None, allow_disabled=False):
147 149 old_data = old_data or {}
148 150
149 151 class _UserGroupForm(formencode.Schema):
150 152 allow_extra_fields = True
151 153 filter_extra_fields = True
152 154
153 155 users_group_name = All(
154 156 v.UnicodeString(strip=True, min=1, not_empty=True),
155 157 v.ValidUserGroup(edit, old_data)
156 158 )
157 159 user_group_description = v.UnicodeString(strip=True, min=1,
158 160 not_empty=False)
159 161
160 162 users_group_active = v.StringBoolean(if_missing=False)
161 163
162 164 if edit:
163 165 # this is user group owner
164 166 user = All(
165 167 v.UnicodeString(not_empty=True),
166 168 v.ValidRepoUser(allow_disabled))
167 169 return _UserGroupForm
168 170
169 171
170 172 def RepoGroupForm(edit=False, old_data=None, available_groups=None,
171 173 can_create_in_root=False, allow_disabled=False):
172 174 old_data = old_data or {}
173 175 available_groups = available_groups or []
174 176
175 177 class _RepoGroupForm(formencode.Schema):
176 178 allow_extra_fields = True
177 179 filter_extra_fields = False
178 180
179 181 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
180 182 v.SlugifyName(),)
181 183 group_description = v.UnicodeString(strip=True, min=1,
182 184 not_empty=False)
183 185 group_copy_permissions = v.StringBoolean(if_missing=False)
184 186
185 187 group_parent_id = v.OneOf(available_groups, hideList=False,
186 188 testValueList=True, not_empty=True)
187 189 enable_locking = v.StringBoolean(if_missing=False)
188 190 chained_validators = [
189 191 v.ValidRepoGroup(edit, old_data, can_create_in_root)]
190 192
191 193 if edit:
192 194 # this is repo group owner
193 195 user = All(
194 196 v.UnicodeString(not_empty=True),
195 197 v.ValidRepoUser(allow_disabled))
196 198
197 199 return _RepoGroupForm
198 200
199 201
200 202 def RegisterForm(edit=False, old_data={}):
201 203 class _RegisterForm(formencode.Schema):
202 204 allow_extra_fields = True
203 205 filter_extra_fields = True
204 206 username = All(
205 207 v.ValidUsername(edit, old_data),
206 208 v.UnicodeString(strip=True, min=1, not_empty=True)
207 209 )
208 210 password = All(
209 211 v.ValidPassword(),
210 212 v.UnicodeString(strip=False, min=6, not_empty=True)
211 213 )
212 214 password_confirmation = All(
213 215 v.ValidPassword(),
214 216 v.UnicodeString(strip=False, min=6, not_empty=True)
215 217 )
216 218 active = v.StringBoolean(if_missing=False)
217 219 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
218 220 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
219 221 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
220 222
221 223 chained_validators = [v.ValidPasswordsMatch()]
222 224
223 225 return _RegisterForm
224 226
225 227
226 228 def PasswordResetForm():
227 229 class _PasswordResetForm(formencode.Schema):
228 230 allow_extra_fields = True
229 231 filter_extra_fields = True
230 232 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
231 233 return _PasswordResetForm
232 234
233 235
234 236 def RepoForm(edit=False, old_data=None, repo_groups=None, landing_revs=None,
235 237 allow_disabled=False):
236 238 old_data = old_data or {}
237 239 repo_groups = repo_groups or []
238 240 landing_revs = landing_revs or []
239 241 supported_backends = BACKENDS.keys()
240 242
241 243 class _RepoForm(formencode.Schema):
242 244 allow_extra_fields = True
243 245 filter_extra_fields = False
244 246 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
245 247 v.SlugifyName(), v.CannotHaveGitSuffix())
246 248 repo_group = All(v.CanWriteGroup(old_data),
247 249 v.OneOf(repo_groups, hideList=True))
248 250 repo_type = v.OneOf(supported_backends, required=False,
249 251 if_missing=old_data.get('repo_type'))
250 252 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
251 253 repo_private = v.StringBoolean(if_missing=False)
252 254 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
253 255 repo_copy_permissions = v.StringBoolean(if_missing=False)
254 256 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
255 257
256 258 repo_enable_statistics = v.StringBoolean(if_missing=False)
257 259 repo_enable_downloads = v.StringBoolean(if_missing=False)
258 260 repo_enable_locking = v.StringBoolean(if_missing=False)
259 261
260 262 if edit:
261 263 # this is repo owner
262 264 user = All(
263 265 v.UnicodeString(not_empty=True),
264 266 v.ValidRepoUser(allow_disabled))
265 267 clone_uri_change = v.UnicodeString(
266 268 not_empty=False, if_missing=v.Missing)
267 269
268 270 chained_validators = [v.ValidCloneUri(),
269 271 v.ValidRepoName(edit, old_data)]
270 272 return _RepoForm
271 273
272 274
273 275 def RepoPermsForm():
274 276 class _RepoPermsForm(formencode.Schema):
275 277 allow_extra_fields = True
276 278 filter_extra_fields = False
277 279 chained_validators = [v.ValidPerms(type_='repo')]
278 280 return _RepoPermsForm
279 281
280 282
281 283 def RepoGroupPermsForm(valid_recursive_choices):
282 284 class _RepoGroupPermsForm(formencode.Schema):
283 285 allow_extra_fields = True
284 286 filter_extra_fields = False
285 287 recursive = v.OneOf(valid_recursive_choices)
286 288 chained_validators = [v.ValidPerms(type_='repo_group')]
287 289 return _RepoGroupPermsForm
288 290
289 291
290 292 def UserGroupPermsForm():
291 293 class _UserPermsForm(formencode.Schema):
292 294 allow_extra_fields = True
293 295 filter_extra_fields = False
294 296 chained_validators = [v.ValidPerms(type_='user_group')]
295 297 return _UserPermsForm
296 298
297 299
298 300 def RepoFieldForm():
299 301 class _RepoFieldForm(formencode.Schema):
300 302 filter_extra_fields = True
301 303 allow_extra_fields = True
302 304
303 305 new_field_key = All(v.FieldKey(),
304 306 v.UnicodeString(strip=True, min=3, not_empty=True))
305 307 new_field_value = v.UnicodeString(not_empty=False, if_missing=u'')
306 308 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
307 309 if_missing='str')
308 310 new_field_label = v.UnicodeString(not_empty=False)
309 311 new_field_desc = v.UnicodeString(not_empty=False)
310 312
311 313 return _RepoFieldForm
312 314
313 315
314 316 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
315 317 repo_groups=[], landing_revs=[]):
316 318 class _RepoForkForm(formencode.Schema):
317 319 allow_extra_fields = True
318 320 filter_extra_fields = False
319 321 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
320 322 v.SlugifyName())
321 323 repo_group = All(v.CanWriteGroup(),
322 324 v.OneOf(repo_groups, hideList=True))
323 325 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
324 326 description = v.UnicodeString(strip=True, min=1, not_empty=True)
325 327 private = v.StringBoolean(if_missing=False)
326 328 copy_permissions = v.StringBoolean(if_missing=False)
327 329 fork_parent_id = v.UnicodeString()
328 330 chained_validators = [v.ValidForkName(edit, old_data)]
329 331 landing_rev = v.OneOf(landing_revs, hideList=True)
330 332
331 333 return _RepoForkForm
332 334
333 335
334 336 def ApplicationSettingsForm():
335 337 class _ApplicationSettingsForm(formencode.Schema):
336 338 allow_extra_fields = True
337 339 filter_extra_fields = False
338 340 rhodecode_title = v.UnicodeString(strip=True, max=40, not_empty=False)
339 341 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
340 342 rhodecode_pre_code = v.UnicodeString(strip=True, min=1, not_empty=False)
341 343 rhodecode_post_code = v.UnicodeString(strip=True, min=1, not_empty=False)
342 344 rhodecode_captcha_public_key = v.UnicodeString(strip=True, min=1, not_empty=False)
343 345 rhodecode_captcha_private_key = v.UnicodeString(strip=True, min=1, not_empty=False)
344 346 rhodecode_create_personal_repo_group = v.StringBoolean(if_missing=False)
345 347 rhodecode_personal_repo_group_pattern = v.UnicodeString(strip=True, min=1, not_empty=False)
346 348
347 349 return _ApplicationSettingsForm
348 350
349 351
350 352 def ApplicationVisualisationForm():
351 353 class _ApplicationVisualisationForm(formencode.Schema):
352 354 allow_extra_fields = True
353 355 filter_extra_fields = False
354 356 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
355 357 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
356 358 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
357 359
358 360 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
359 361 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
360 362 rhodecode_dashboard_items = v.Int(min=5, not_empty=True)
361 363 rhodecode_admin_grid_items = v.Int(min=5, not_empty=True)
362 364 rhodecode_show_version = v.StringBoolean(if_missing=False)
363 365 rhodecode_use_gravatar = v.StringBoolean(if_missing=False)
364 366 rhodecode_markup_renderer = v.OneOf(['markdown', 'rst'])
365 367 rhodecode_gravatar_url = v.UnicodeString(min=3)
366 368 rhodecode_clone_uri_tmpl = v.UnicodeString(min=3)
367 369 rhodecode_support_url = v.UnicodeString()
368 370 rhodecode_show_revision_number = v.StringBoolean(if_missing=False)
369 371 rhodecode_show_sha_length = v.Int(min=4, not_empty=True)
370 372
371 373 return _ApplicationVisualisationForm
372 374
373 375
374 376 class _BaseVcsSettingsForm(formencode.Schema):
375 377 allow_extra_fields = True
376 378 filter_extra_fields = False
377 379 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
378 380 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
379 381 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
380 382
381 383 # PR/Code-review
382 384 rhodecode_pr_merge_enabled = v.StringBoolean(if_missing=False)
383 385 rhodecode_use_outdated_comments = v.StringBoolean(if_missing=False)
384 386
385 387 # hg
386 388 extensions_largefiles = v.StringBoolean(if_missing=False)
387 389 extensions_evolve = v.StringBoolean(if_missing=False)
388 390 phases_publish = v.StringBoolean(if_missing=False)
389 391 rhodecode_hg_use_rebase_for_merging = v.StringBoolean(if_missing=False)
390 392
391 393 # git
392 394 vcs_git_lfs_enabled = v.StringBoolean(if_missing=False)
393 395
394 396 # svn
395 397 vcs_svn_proxy_http_requests_enabled = v.StringBoolean(if_missing=False)
396 398 vcs_svn_proxy_http_server_url = v.UnicodeString(strip=True, if_missing=None)
397 399
398 400
399 401 def ApplicationUiSettingsForm():
400 402 class _ApplicationUiSettingsForm(_BaseVcsSettingsForm):
401 403 web_push_ssl = v.StringBoolean(if_missing=False)
402 404 paths_root_path = All(
403 405 v.ValidPath(),
404 406 v.UnicodeString(strip=True, min=1, not_empty=True)
405 407 )
406 408 largefiles_usercache = All(
407 409 v.ValidPath(),
408 410 v.UnicodeString(strip=True, min=2, not_empty=True))
409 411 vcs_git_lfs_store_location = All(
410 412 v.ValidPath(),
411 413 v.UnicodeString(strip=True, min=2, not_empty=True))
412 414 extensions_hgsubversion = v.StringBoolean(if_missing=False)
413 415 extensions_hggit = v.StringBoolean(if_missing=False)
414 416 new_svn_branch = v.ValidSvnPattern(section='vcs_svn_branch')
415 417 new_svn_tag = v.ValidSvnPattern(section='vcs_svn_tag')
416 418
417 419 return _ApplicationUiSettingsForm
418 420
419 421
420 422 def RepoVcsSettingsForm(repo_name):
421 423 class _RepoVcsSettingsForm(_BaseVcsSettingsForm):
422 424 inherit_global_settings = v.StringBoolean(if_missing=False)
423 425 new_svn_branch = v.ValidSvnPattern(
424 426 section='vcs_svn_branch', repo_name=repo_name)
425 427 new_svn_tag = v.ValidSvnPattern(
426 428 section='vcs_svn_tag', repo_name=repo_name)
427 429
428 430 return _RepoVcsSettingsForm
429 431
430 432
431 433 def LabsSettingsForm():
432 434 class _LabSettingsForm(formencode.Schema):
433 435 allow_extra_fields = True
434 436 filter_extra_fields = False
435 437
436 438 return _LabSettingsForm
437 439
438 440
439 441 def ApplicationPermissionsForm(
440 442 register_choices, password_reset_choices, extern_activate_choices):
441 443 class _DefaultPermissionsForm(formencode.Schema):
442 444 allow_extra_fields = True
443 445 filter_extra_fields = True
444 446
445 447 anonymous = v.StringBoolean(if_missing=False)
446 448 default_register = v.OneOf(register_choices)
447 449 default_register_message = v.UnicodeString()
448 450 default_password_reset = v.OneOf(password_reset_choices)
449 451 default_extern_activate = v.OneOf(extern_activate_choices)
450 452
451 453 return _DefaultPermissionsForm
452 454
453 455
454 456 def ObjectPermissionsForm(repo_perms_choices, group_perms_choices,
455 457 user_group_perms_choices):
456 458 class _ObjectPermissionsForm(formencode.Schema):
457 459 allow_extra_fields = True
458 460 filter_extra_fields = True
459 461 overwrite_default_repo = v.StringBoolean(if_missing=False)
460 462 overwrite_default_group = v.StringBoolean(if_missing=False)
461 463 overwrite_default_user_group = v.StringBoolean(if_missing=False)
462 464 default_repo_perm = v.OneOf(repo_perms_choices)
463 465 default_group_perm = v.OneOf(group_perms_choices)
464 466 default_user_group_perm = v.OneOf(user_group_perms_choices)
465 467
466 468 return _ObjectPermissionsForm
467 469
468 470
469 471 def UserPermissionsForm(create_choices, create_on_write_choices,
470 472 repo_group_create_choices, user_group_create_choices,
471 473 fork_choices, inherit_default_permissions_choices):
472 474 class _DefaultPermissionsForm(formencode.Schema):
473 475 allow_extra_fields = True
474 476 filter_extra_fields = True
475 477
476 478 anonymous = v.StringBoolean(if_missing=False)
477 479
478 480 default_repo_create = v.OneOf(create_choices)
479 481 default_repo_create_on_write = v.OneOf(create_on_write_choices)
480 482 default_user_group_create = v.OneOf(user_group_create_choices)
481 483 default_repo_group_create = v.OneOf(repo_group_create_choices)
482 484 default_fork_create = v.OneOf(fork_choices)
483 485 default_inherit_default_permissions = v.OneOf(inherit_default_permissions_choices)
484 486
485 487 return _DefaultPermissionsForm
486 488
487 489
488 490 def UserIndividualPermissionsForm():
489 491 class _DefaultPermissionsForm(formencode.Schema):
490 492 allow_extra_fields = True
491 493 filter_extra_fields = True
492 494
493 495 inherit_default_permissions = v.StringBoolean(if_missing=False)
494 496
495 497 return _DefaultPermissionsForm
496 498
497 499
498 500 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
499 501 class _DefaultsForm(formencode.Schema):
500 502 allow_extra_fields = True
501 503 filter_extra_fields = True
502 504 default_repo_type = v.OneOf(supported_backends)
503 505 default_repo_private = v.StringBoolean(if_missing=False)
504 506 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
505 507 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
506 508 default_repo_enable_locking = v.StringBoolean(if_missing=False)
507 509
508 510 return _DefaultsForm
509 511
510 512
511 513 def AuthSettingsForm():
512 514 class _AuthSettingsForm(formencode.Schema):
513 515 allow_extra_fields = True
514 516 filter_extra_fields = True
515 517 auth_plugins = All(v.ValidAuthPlugins(),
516 518 v.UniqueListFromString()(not_empty=True))
517 519
518 520 return _AuthSettingsForm
519 521
520 522
521 523 def UserExtraEmailForm():
522 524 class _UserExtraEmailForm(formencode.Schema):
523 525 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
524 526 return _UserExtraEmailForm
525 527
526 528
527 529 def UserExtraIpForm():
528 530 class _UserExtraIpForm(formencode.Schema):
529 531 ip = v.ValidIp()(not_empty=True)
530 532 return _UserExtraIpForm
531 533
532 534
533 535
534 536 def PullRequestForm(repo_id):
535 537 class ReviewerForm(formencode.Schema):
536 538 user_id = v.Int(not_empty=True)
537 539 reasons = All()
538 540 mandatory = v.StringBoolean()
539 541
540 542 class _PullRequestForm(formencode.Schema):
541 543 allow_extra_fields = True
542 544 filter_extra_fields = True
543 545
544 546 common_ancestor = v.UnicodeString(strip=True, required=True)
545 547 source_repo = v.UnicodeString(strip=True, required=True)
546 548 source_ref = v.UnicodeString(strip=True, required=True)
547 549 target_repo = v.UnicodeString(strip=True, required=True)
548 550 target_ref = v.UnicodeString(strip=True, required=True)
549 551 revisions = All(#v.NotReviewedRevisions(repo_id)(),
550 552 v.UniqueList()(not_empty=True))
551 553 review_members = formencode.ForEach(ReviewerForm())
552 554 pullrequest_title = v.UnicodeString(strip=True, required=True)
553 555 pullrequest_desc = v.UnicodeString(strip=True, required=False)
554 556
555 557 return _PullRequestForm
556 558
557 559
558 560 def IssueTrackerPatternsForm():
559 561 class _IssueTrackerPatternsForm(formencode.Schema):
560 562 allow_extra_fields = True
561 563 filter_extra_fields = False
562 564 chained_validators = [v.ValidPattern()]
563 565 return _IssueTrackerPatternsForm
@@ -1,116 +1,116 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${_('Authentication Plugins')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 32 % for item in resource.get_root().get_nav_list():
33 33 <li ${'class=active' if item == resource else ''}>
34 34 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
35 35 </li>
36 36 % endfor
37 37 </ul>
38 38 </div>
39 39
40 40 <div class="main-content-full-width">
41 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
41 ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)}
42 42 <div class="form">
43 43
44 44 <div class="panel panel-default">
45 45
46 46 <div class="panel-heading">
47 47 <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3>
48 48 </div>
49 49
50 50 <div class="fields panel-body">
51 51
52 52 <div class="field">
53 53 <div class="label">${_("Enabled Plugins")}</div>
54 54 <div class="textarea text-area editor">
55 55 ${h.textarea('auth_plugins',cols=23,rows=5,class_="medium")}
56 56 </div>
57 57 <p class="help-block">
58 58 ${_('Add a list of plugins, separated by commas. '
59 59 'The order of the plugins is also the order in which '
60 60 'RhodeCode Enterprise will try to authenticate a user.')}
61 61 </p>
62 62 </div>
63 63
64 64 <div class="field">
65 65 <div class="label">${_('Available Built-in Plugins')}</div>
66 66 <ul class="auth_plugins">
67 67 %for plugin in available_plugins:
68 68 <li>
69 69 <div class="auth_buttons">
70 70 <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${'btn-success' if plugin.get_id() in enabled_plugins else ''}">
71 71 ${_('enabled') if plugin.get_id() in enabled_plugins else _('disabled')}
72 72 </span>
73 73 ${plugin.get_display_name()} (${plugin.get_id()})
74 74 </div>
75 75 </li>
76 76 %endfor
77 77 </ul>
78 78 </div>
79 79
80 80 <div class="buttons">
81 81 ${h.submit('save',_('Save'),class_="btn")}
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 ${h.end_form()}
87 87 </div>
88 88 </div>
89 89 </div>
90 90
91 91 <script>
92 92 $('.toggle-plugin').click(function(e){
93 93 var auth_plugins_input = $('#auth_plugins');
94 94 var notEmpty = function(element, index, array) {
95 95 return (element != "");
96 96 };
97 97 var elems = auth_plugins_input.val().split(',').filter(notEmpty);
98 98 var cur_button = e.currentTarget;
99 99 var plugin_id = $(cur_button).attr('plugin_id');
100 100 if($(cur_button).hasClass('btn-success')){
101 101 elems.splice(elems.indexOf(plugin_id), 1);
102 102 auth_plugins_input.val(elems.join(','));
103 103 $(cur_button).removeClass('btn-success');
104 104 cur_button.innerHTML = _gettext('disabled');
105 105 }
106 106 else{
107 107 if(elems.indexOf(plugin_id) == -1){
108 108 elems.push(plugin_id);
109 109 }
110 110 auth_plugins_input.val(elems.join(','));
111 111 $(cur_button).addClass('btn-success');
112 112 cur_button.innerHTML = _gettext('enabled');
113 113 }
114 114 });
115 115 </script>
116 116 </%def>
@@ -1,118 +1,118 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
13 13 &raquo;
14 14 ${h.link_to(_('Authentication Plugins'),request.resource_path(resource.__parent__, route_name='auth_home'))}
15 15 &raquo;
16 16 ${resource.display_name}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='admin')}
21 21 </%def>
22 22
23 23 <%def name="main()">
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.breadcrumbs()}
27 27 </div>
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 ## TODO: This is repeated in the auth root template and should be merged
31 31 ## into a single solution.
32 32 <div class="sidebar">
33 33 <ul class="nav nav-pills nav-stacked">
34 34 % for item in resource.get_root().get_nav_list():
35 35 <li ${'class=active' if item == resource else ''}>
36 36 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
37 37 </li>
38 38 % endfor
39 39 </ul>
40 40 </div>
41 41
42 42 <div class="main-content-full-width">
43 43 <div class="panel panel-default">
44 44 <div class="panel-heading">
45 45 <h3 class="panel-title">${_('Plugin')}: ${resource.display_name}</h3>
46 46 </div>
47 47 <div class="panel-body">
48 48 <div class="plugin_form">
49 49 <div class="fields">
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
50 ${h.secure_form(request.resource_path(resource, route_name='auth_home'), request=request)}
51 51 <div class="form">
52 52
53 53 %for node in plugin.get_settings_schema():
54 54 <% label_css_class = ("label-checkbox" if (node.widget == "bool") else "") %>
55 55 <div class="field">
56 56 <div class="label ${label_css_class}"><label for="${node.name}">${node.title}</label></div>
57 57 <div class="input">
58 58 %if node.widget in ["string", "int", "unicode"]:
59 59 ${h.text(node.name, defaults.get(node.name), class_="medium")}
60 60 %elif node.widget == "password":
61 61 ${h.password(node.name, defaults.get(node.name), class_="medium")}
62 62 %elif node.widget == "bool":
63 63 <div class="checkbox">${h.checkbox(node.name, True, checked=defaults.get(node.name))}</div>
64 64 %elif node.widget == "select":
65 65 ${h.select(node.name, defaults.get(node.name), node.validator.choices)}
66 66 %elif node.widget == "readonly":
67 67 ${node.default}
68 68 %else:
69 69 This field is of type ${node.typ}, which cannot be displayed. Must be one of [string|int|bool|select].
70 70 %endif
71 71 %if node.name in errors:
72 72 <span class="error-message">${errors.get(node.name)}</span>
73 73 <br />
74 74 %endif
75 75 <p class="help-block pre-formatting">${node.description}</p>
76 76 </div>
77 77 </div>
78 78 %endfor
79 79
80 80 ## Allow derived templates to add something below the form
81 81 ## input fields
82 82 %if hasattr(next, 'below_form_fields'):
83 83 ${next.below_form_fields()}
84 84 %endif
85 85
86 86 <div class="buttons">
87 87 ${h.submit('save',_('Save'),class_="btn")}
88 88 </div>
89 89
90 90 </div>
91 91 ${h.end_form()}
92 92 </div>
93 93 </div>
94 94 </div>
95 95 </div>
96 96 </div>
97 97
98 98 </div>
99 99 </div>
100 100
101 101 ## TODO: Ugly hack to get ldap select elements to work.
102 102 ## Find a solution to integrate this nicely.
103 103 <script>
104 104 $(document).ready(function() {
105 105 var select2Options = {
106 106 containerCssClass: 'drop-menu',
107 107 dropdownCssClass: 'drop-menu-dropdown',
108 108 dropdownAutoWidth: true,
109 109 minimumResultsForSearch: -1
110 110 };
111 111 $("#tls_kind").select2(select2Options);
112 112 $("#tls_reqcert").select2(select2Options);
113 113 $("#search_scope").select2(select2Options);
114 114 $("#group_extraction_type").select2(select2Options);
115 115 $("#admin_groups_sync").select2(select2Options);
116 116 });
117 117 </script>
118 118 </%def>
@@ -1,139 +1,139 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('Edit Gist')} &middot; ${c.gist.gist_access_id}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='gists')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25
26 26 <div class="table">
27 27
28 28 <div id="files_data">
29 ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', method='POST')}
29 ${h.secure_form(h.route_path('gist_update', gist_id=c.gist.gist_access_id), id='eform', method='POST', request=request)}
30 30 <div>
31 31 <input type="hidden" value="${c.file_last_commit.raw_id}" name="parent_hash">
32 32 <textarea id="description" name="description"
33 33 placeholder="${_('Gist description ...')}">${c.gist.gist_description}</textarea>
34 34 <div>
35 35 <span class="gist-gravatar">
36 36 ${self.gravatar(h.email_or_none(c.rhodecode_user.full_contact), 30)}
37 37 </span>
38 38 <label for='lifetime'>${_('Gist lifetime')}</label>
39 39 ${h.dropdownmenu('lifetime', '0', c.lifetime_options)}
40 40
41 41 <label for='gist_acl_level'>${_('Gist access level')}</label>
42 42 ${h.dropdownmenu('gist_acl_level', c.gist.acl_level, c.acl_options)}
43 43 </div>
44 44 </div>
45 45
46 46 ## peppercorn schema
47 47 <input type="hidden" name="__start__" value="nodes:sequence"/>
48 48 % for cnt, file in enumerate(c.files):
49 49 <input type="hidden" name="__start__" value="file:mapping"/>
50 50 <div id="codeblock" class="codeblock" >
51 51 <div class="code-header">
52 52 <div class="form">
53 53 <div class="fields">
54 54 <input type="hidden" name="filename_org" value="${file.path}" >
55 55 <input id="filename_${h.FID('f',file.path)}" name="filename" size="30" type="text" value="${file.path}">
56 56 ${h.dropdownmenu('mimetype' ,'plain',[('plain',_('plain'))],enable_filter=True, id='mimetype_'+h.FID('f',file.path))}
57 57 </div>
58 58 </div>
59 59 </div>
60 60 <div class="editor_container">
61 61 <pre id="editor_pre"></pre>
62 62 <textarea id="editor_${h.FID('f',file.path)}" name="content" >${file.content}</textarea>
63 63 </div>
64 64 </div>
65 65 <input type="hidden" name="__end__" />
66 66
67 67 ## dynamic edit box.
68 68 <script type="text/javascript">
69 69 $(document).ready(function(){
70 70 var myCodeMirror = initCodeMirror(
71 71 "editor_${h.FID('f',file.path)}", '');
72 72
73 73 var modes_select = $("#mimetype_${h.FID('f',file.path)}");
74 74 fillCodeMirrorOptions(modes_select);
75 75
76 76 // try to detect the mode based on the file we edit
77 77 var mimetype = "${file.mimetype}";
78 78 var detected_mode = detectCodeMirrorMode(
79 79 "${file.path}", mimetype);
80 80
81 81 if(detected_mode){
82 82 $(modes_select).select2("val", mimetype);
83 83 $(modes_select).change();
84 84 setCodeMirrorMode(myCodeMirror, detected_mode);
85 85 }
86 86
87 87 var filename_selector = "#filename_${h.FID('f',file.path)}";
88 88 // on change of select field set mode
89 89 setCodeMirrorModeFromSelect(
90 90 modes_select, filename_selector, myCodeMirror, null);
91 91
92 92 // on entering the new filename set mode, from given extension
93 93 setCodeMirrorModeFromInput(
94 94 modes_select, filename_selector, myCodeMirror, null);
95 95 });
96 96 </script>
97 97 %endfor
98 98 <input type="hidden" name="__end__" />
99 99
100 100 <div class="pull-right">
101 101 ${h.submit('update',_('Update Gist'),class_="btn btn-success")}
102 102 <a class="btn" href="${h.route_path('gist_show', gist_id=c.gist.gist_access_id)}">${_('Cancel')}</a>
103 103 </div>
104 104 ${h.end_form()}
105 105 </div>
106 106 </div>
107 107
108 108 </div>
109 109 <script>
110 110 $('#update').on('click', function(e){
111 111 e.preventDefault();
112 112
113 113 $(this).val('Updating...');
114 114 $(this).attr('disabled', 'disabled');
115 115 // check for newer version.
116 116 $.ajax({
117 117 url: "${h.route_path('gist_edit_check_revision', gist_id=c.gist.gist_access_id)}",
118 118 data: {
119 119 'revision': '${c.file_last_commit.raw_id}'
120 120 },
121 121 dataType: 'json',
122 122 type: 'GET',
123 123 success: function(data) {
124 124 if(data.success === false){
125 125 message = '${h.literal(_('Gist was updated since you started editing. Copy your changes and click %(here)s to reload the new version.')
126 126 % {'here': h.link_to('here', h.route_path('gist_edit', gist_id=c.gist.gist_access_id))})}'
127 127 alertMessage = [{"message": {
128 128 "message": message, "force": "true", "level": "warning"}}];
129 129 $.Topic('/notifications').publish(alertMessage[0]);
130 130 }
131 131 else{
132 132 $('#eform').submit();
133 133 }
134 134 }
135 135 });
136 136 })
137 137
138 138 </script>
139 139 </%def>
@@ -1,86 +1,86 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="title()">
5 5 ${_('New Gist')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${_('New Gist')}
13 13 </%def>
14 14
15 15 <%def name="menu_bar_nav()">
16 16 ${self.menu_items(active='gists')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25
26 26 <div class="table">
27 27 <div id="files_data">
28 ${h.secure_form(h.route_path('gists_create'), id='eform', method='POST')}
28 ${h.secure_form(h.route_path('gists_create'), id='eform', method='POST', request=request)}
29 29 <div>
30 30 <textarea id="description" name="description" placeholder="${_('Gist description ...')}"></textarea>
31 31
32 32 <span class="gist-gravatar">
33 33 ${self.gravatar(c.rhodecode_user.email, 30)}
34 34 </span>
35 35 <label for='gistid'>${_('Gist id')}</label>
36 36 ${h.text('gistid', placeholder=_('Auto generated'))}
37 37
38 38 <label for='lifetime'>${_('Gist lifetime')}</label>
39 39 ${h.dropdownmenu('lifetime', '', c.lifetime_options)}
40 40
41 41 <label for='acl_level'>${_('Gist access level')}</label>
42 42 ${h.dropdownmenu('gist_acl_level', '', c.acl_options)}
43 43
44 44 </div>
45 45 <div id="codeblock" class="codeblock">
46 46 <div class="code-header">
47 47 <div class="form">
48 48 <div class="fields">
49 49 ${h.text('filename', size=30, placeholder=_('name this file...'))}
50 50 ${h.dropdownmenu('mimetype','plain',[('plain',_('plain'))],enable_filter=True)}
51 51 </div>
52 52 </div>
53 53 </div>
54 54 <div id="editor_container">
55 55 <div id="editor_pre"></div>
56 56 <textarea id="editor" name="content" ></textarea>
57 57 </div>
58 58 </div>
59 59 <div class="pull-right">
60 60 ${h.submit('private',_('Create Private Gist'),class_="btn")}
61 61 ${h.submit('public',_('Create Public Gist'),class_="btn")}
62 62 ${h.reset('reset',_('Reset'),class_="btn")}
63 63 </div>
64 64 ${h.end_form()}
65 65 </div>
66 66 </div>
67 67
68 68 </div>
69 69
70 70 <script type="text/javascript">
71 71 var myCodeMirror = initCodeMirror('editor', '');
72 72
73 73 var modes_select = $('#mimetype');
74 74 fillCodeMirrorOptions(modes_select);
75 75
76 76 var filename_selector = '#filename';
77 77 // on change of select field set mode
78 78 setCodeMirrorModeFromSelect(
79 79 modes_select, filename_selector, myCodeMirror, null);
80 80
81 81 // on entering the new filename set mode, from given extension
82 82 setCodeMirrorModeFromInput(
83 83 modes_select, filename_selector, myCodeMirror, null);
84 84
85 85 </script>
86 86 </%def>
@@ -1,110 +1,110 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.mako"/>
3 3
4 4 <%def name="robots()">
5 5 %if c.gist.gist_type != 'public':
6 6 <meta name="robots" content="noindex, nofollow">
7 7 %else:
8 8 ${parent.robots()}
9 9 %endif
10 10 </%def>
11 11
12 12 <%def name="title()">
13 13 ${_('Gist')} &middot; ${c.gist.gist_access_id}
14 14 %if c.rhodecode_name:
15 15 &middot; ${h.branding(c.rhodecode_name)}
16 16 %endif
17 17 </%def>
18 18
19 19 <%def name="breadcrumbs_links()">
20 20 ${_('Gist')} &middot; ${c.gist.gist_access_id}
21 21 </%def>
22 22
23 23 <%def name="menu_bar_nav()">
24 24 ${self.menu_items(active='gists')}
25 25 </%def>
26 26
27 27 <%def name="main()">
28 28 <div class="box">
29 29 <!-- box / title -->
30 30 <div class="title">
31 31 ${self.breadcrumbs()}
32 32 %if c.rhodecode_user.username != h.DEFAULT_USER:
33 33 <ul class="links">
34 34 <li>
35 35 <a href="${h.route_path('gists_new')}" class="btn btn-primary">${_(u'Create New Gist')}</a>
36 36 </li>
37 37 </ul>
38 38 %endif
39 39 </div>
40 40 <code>${c.gist.gist_url()}</code>
41 41 <div class="table">
42 42 <div id="files_data">
43 43 <div id="codeblock" class="codeblock">
44 44 <div class="code-header">
45 45 <div class="stats">
46 46 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
47 47 <div class="remove_gist">
48 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), method='POST')}
48 ${h.secure_form(h.route_path('gist_delete', gist_id=c.gist.gist_access_id), method='POST', request=request)}
49 49 ${h.submit('remove_gist', _('Delete'),class_="btn btn-mini btn-danger",onclick="return confirm('"+_('Confirm to delete this Gist')+"');")}
50 50 ${h.end_form()}
51 51 </div>
52 52 %endif
53 53 <div class="buttons">
54 54 ## only owner should see that
55 55 %if h.HasPermissionAny('hg.admin')() or c.gist.gist_owner == c.rhodecode_user.user_id:
56 56 ${h.link_to(_('Edit'), h.route_path('gist_edit', gist_id=c.gist.gist_access_id), class_="btn btn-mini")}
57 57 %endif
58 58 ${h.link_to(_('Show as Raw'), h.route_path('gist_show_formatted', gist_id=c.gist.gist_access_id, revision='tip', format='raw'), class_="btn btn-mini")}
59 59 </div>
60 60 <div class="left" >
61 61 %if c.gist.gist_type != 'public':
62 62 <span class="tag tag-ok disabled">${_('Private Gist')}</span>
63 63 %endif
64 64 <span> ${c.gist.gist_description}</span>
65 65 <span>${_('Expires')}:
66 66 %if c.gist.gist_expires == -1:
67 67 ${_('never')}
68 68 %else:
69 69 ${h.age_component(h.time_to_utcdatetime(c.gist.gist_expires))}
70 70 %endif
71 71 </span>
72 72 </div>
73 73 </div>
74 74
75 75 <div class="author">
76 76 <div title="${h.tooltip(c.file_last_commit.author)}">
77 77 ${self.gravatar_with_user(c.file_last_commit.author, 16)} - ${_('created')} ${h.age_component(c.file_last_commit.date)}
78 78 </div>
79 79
80 80 </div>
81 81 <div class="commit">${h.urlify_commit_message(c.file_last_commit.message, None)}</div>
82 82 </div>
83 83
84 84 ## iterate over the files
85 85 % for file in c.files:
86 86 <% renderer = c.render and h.renderer_from_filename(file.path, exclude=['.txt', '.TXT'])%>
87 87 <!--
88 88 <div id="${h.FID('G', file.path)}" class="stats" >
89 89 <a href="${c.gist.gist_url()}">ΒΆ</a>
90 90 <b >${file.path}</b>
91 91 <div>
92 92 ${h.link_to(_('Show as raw'), h.route_path('gist_show_formatted_path', gist_id=c.gist.gist_access_id, revision=file.commit.raw_id, format='raw', f_path=file.path), class_="btn btn-mini")}
93 93 </div>
94 94 </div>
95 95 -->
96 96 <div class="code-body textarea text-area editor">
97 97 %if renderer:
98 98 ${h.render(file.content, renderer=renderer)}
99 99 %else:
100 100 ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
101 101 %endif
102 102 </div>
103 103 %endfor
104 104 </div>
105 105 </div>
106 106 </div>
107 107
108 108
109 109 </div>
110 110 </%def>
@@ -1,160 +1,160 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="apikeys_wrap">
7 7 <p>
8 8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 10 </p>
11 11 <table class="rctable auth_tokens">
12 12 <tr>
13 13 <th>${_('Token')}</th>
14 14 <th>${_('Scope')}</th>
15 15 <th>${_('Description')}</th>
16 16 <th>${_('Role')}</th>
17 17 <th>${_('Expiration')}</th>
18 18 <th>${_('Action')}</th>
19 19 </tr>
20 20 %if c.user_auth_tokens:
21 21 %for auth_token in c.user_auth_tokens:
22 22 <tr class="${'expired' if auth_token.expired else ''}">
23 23 <td class="truncate-wrap td-authtoken">
24 24 <div class="user_auth_tokens truncate autoexpand">
25 25 <code>${auth_token.api_key}</code>
26 26 </div>
27 27 </td>
28 28 <td class="td">${auth_token.scope_humanized}</td>
29 29 <td class="td-wrap">${auth_token.description}</td>
30 30 <td class="td-tags">
31 31 <span class="tag disabled">${auth_token.role_humanized}</span>
32 32 </td>
33 33 <td class="td-exp">
34 34 %if auth_token.expires == -1:
35 35 ${_('never')}
36 36 %else:
37 37 %if auth_token.expired:
38 38 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
39 39 %else:
40 40 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
41 41 %endif
42 42 %endif
43 43 </td>
44 44 <td class="td-action">
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='post')}
45 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), method='POST', request=request)}
46 46 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
47 47 <button class="btn btn-link btn-danger" type="submit"
48 48 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
49 49 ${_('Delete')}
50 50 </button>
51 51 ${h.end_form()}
52 52 </td>
53 53 </tr>
54 54 %endfor
55 55 %else:
56 56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
57 57 %endif
58 58 </table>
59 59 </div>
60 60
61 61 <div class="user_auth_tokens">
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='POST', request=request)}
63 63 <div class="form form-vertical">
64 64 <!-- fields -->
65 65 <div class="fields">
66 66 <div class="field">
67 67 <div class="label">
68 68 <label for="new_email">${_('New authentication token')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 ${h.text('description', class_='medium', placeholder=_('Description'))}
72 72 ${h.select('lifetime', '', c.lifetime_options)}
73 73 ${h.select('role', '', c.role_options)}
74 74
75 75 % if c.allow_scoped_tokens:
76 76 ${h.hidden('scope_repo_id')}
77 77 % else:
78 78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
79 79 % endif
80 80 </div>
81 81 <p class="help-block">
82 82 ${_('Repository scope works only with tokens with VCS type.')}
83 83 </p>
84 84 </div>
85 85 <div class="buttons">
86 86 ${h.submit('save',_('Add'),class_="btn")}
87 87 ${h.reset('reset',_('Reset'),class_="btn")}
88 88 </div>
89 89 </div>
90 90 </div>
91 91 ${h.end_form()}
92 92 </div>
93 93 </div>
94 94 </div>
95 95 <script>
96 96 $(document).ready(function(){
97 97
98 98 var select2Options = {
99 99 'containerCssClass': "drop-menu",
100 100 'dropdownCssClass': "drop-menu-dropdown",
101 101 'dropdownAutoWidth': true
102 102 };
103 103 $("#lifetime").select2(select2Options);
104 104 $("#role").select2(select2Options);
105 105
106 106 var repoFilter = function(data) {
107 107 var results = [];
108 108
109 109 if (!data.results[0]) {
110 110 return data
111 111 }
112 112
113 113 $.each(data.results[0].children, function() {
114 114 // replace name to ID for submision
115 115 this.id = this.obj.repo_id;
116 116 results.push(this);
117 117 });
118 118
119 119 data.results[0].children = results;
120 120 return data;
121 121 };
122 122
123 123 $("#scope_repo_id_disabled").select2(select2Options);
124 124
125 125 $("#scope_repo_id").select2({
126 126 cachedDataSource: {},
127 127 minimumInputLength: 2,
128 128 placeholder: "${_('repository scope')}",
129 129 dropdownAutoWidth: true,
130 130 containerCssClass: "drop-menu",
131 131 dropdownCssClass: "drop-menu-dropdown",
132 132 formatResult: formatResult,
133 133 query: $.debounce(250, function(query){
134 134 self = this;
135 135 var cacheKey = query.term;
136 136 var cachedData = self.cachedDataSource[cacheKey];
137 137
138 138 if (cachedData) {
139 139 query.callback({results: cachedData.results});
140 140 } else {
141 141 $.ajax({
142 142 url: pyroutes.url('repo_list_data'),
143 143 data: {'query': query.term},
144 144 dataType: 'json',
145 145 type: 'GET',
146 146 success: function(data) {
147 147 data = repoFilter(data);
148 148 self.cachedDataSource[cacheKey] = data;
149 149 query.callback({results: data.results});
150 150 },
151 151 error: function(data, textStatus, errorThrown) {
152 152 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
153 153 }
154 154 })
155 155 }
156 156 })
157 157 });
158 158
159 159 });
160 160 </script>
@@ -1,72 +1,72 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Account Emails')}</h3>
6 6 </div>
7 7
8 8 <div class="panel-body">
9 9 <div class="emails_wrap">
10 10 <table class="rctable account_emails">
11 11 <tr>
12 12 <td class="td-user">
13 13 ${base.gravatar(c.user.email, 16)}
14 14 <span class="user email">${c.user.email}</span>
15 15 </td>
16 16 <td class="td-tags">
17 17 <span class="tag tag1">${_('Primary')}</span>
18 18 </td>
19 19 </tr>
20 20 %if c.user_email_map:
21 21 %for em in c.user_email_map:
22 22 <tr>
23 23 <td class="td-user">
24 24 ${base.gravatar(em.email, 16)}
25 25 <span class="user email">${em.email}</span>
26 26 </td>
27 27 <td class="td-action">
28 ${h.secure_form(h.route_path('my_account_emails_delete'), method='POST')}
28 ${h.secure_form(h.route_path('my_account_emails_delete'), method='POST', request=request)}
29 29 ${h.hidden('del_email_id',em.email_id)}
30 30 <button class="btn btn-link btn-danger" type="submit" id="${'remove_email_%s'.format(em.email_id)}"
31 31 onclick="return confirm('${_('Confirm to delete this email: {}').format(em.email)}');">
32 32 ${_('Delete')}
33 33 </button>
34 34 ${h.end_form()}
35 35 </td>
36 36 </tr>
37 37 %endfor
38 38 %else:
39 39 <tr class="noborder">
40 40 <td colspan="3">
41 41 <div class="td-email">
42 42 ${_('No additional emails specified')}
43 43 </div>
44 44 </td>
45 45 </tr>
46 46 %endif
47 47 </table>
48 48 </div>
49 49
50 50 <div>
51 ${h.secure_form(h.route_path('my_account_emails_add'), method='POST')}
51 ${h.secure_form(h.route_path('my_account_emails_add'), method='POST', request=request)}
52 52 <div class="form">
53 53 <!-- fields -->
54 54 <div class="fields">
55 55 <div class="field">
56 56 <div class="label">
57 57 <label for="new_email">${_('New email address')}:</label>
58 58 </div>
59 59 <div class="input">
60 60 ${h.text('new_email', class_='medium')}
61 61 </div>
62 62 </div>
63 63 <div class="buttons">
64 64 ${h.submit('save',_('Add'),class_="btn")}
65 65 ${h.reset('reset',_('Reset'),class_="btn")}
66 66 </div>
67 67 </div>
68 68 </div>
69 69 ${h.end_form()}
70 70 </div>
71 71 </div>
72 72 </div>
@@ -1,113 +1,113 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2 <div class="panel panel-default user-profile">
3 3 <div class="panel-heading">
4 4 <h3 class="panel-title">${_('My Profile')}</h3>
5 5 <a href="${h.route_path('my_account_profile')}" class="panel-edit">Close</a>
6 6 </div>
7 7
8 8 <div class="panel-body">
9 ${h.secure_form(h.route_path('my_account_update'), class_='form', method='POST')}
9 ${h.secure_form(h.route_path('my_account_update'), class_='form', method='POST', request=request)}
10 10 <% readonly = None %>
11 11 <% disabled = "" %>
12 12
13 13 % if c.extern_type != 'rhodecode':
14 14 <% readonly = "readonly" %>
15 15 <% disabled = "disabled" %>
16 16 <div class="infoform">
17 17 <div class="fields">
18 18 <p>${_('Your user account details are managed by an external source. Details cannot be managed here.')}
19 19 <br/>${_('Source type')}: <strong>${c.extern_type}</strong>
20 20 </p>
21 21
22 22 <div class="field">
23 23 <div class="label">
24 24 <label for="username">${_('Username')}:</label>
25 25 </div>
26 26 <div class="input">
27 27 ${h.text('username', class_='input-valuedisplay', readonly=readonly)}
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="name">${_('First Name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('firstname', class_='input-valuedisplay', readonly=readonly)}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label">
42 42 <label for="lastname">${_('Last Name')}:</label>
43 43 </div>
44 44 <div class="input-valuedisplay">
45 45 ${h.text('lastname', class_='input-valuedisplay', readonly=readonly)}
46 46 </div>
47 47 </div>
48 48 </div>
49 49 </div>
50 50 % else:
51 51 <div class="form">
52 52 <div class="fields">
53 53 <div class="field">
54 54 <div class="label photo">
55 55 ${_('Photo')}:
56 56 </div>
57 57 <div class="input profile">
58 58 %if c.visual.use_gravatar:
59 59 ${base.gravatar(c.user.email, 100)}
60 60 <p class="help-block">${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
61 61 %else:
62 62 ${base.gravatar(c.user.email, 20)}
63 63 ${_('Avatars are disabled')}
64 64 %endif
65 65 </div>
66 66 </div>
67 67 <div class="field">
68 68 <div class="label">
69 69 <label for="username">${_('Username')}:</label>
70 70 </div>
71 71 <div class="input">
72 72 ${h.text('username', class_='medium%s' % disabled, readonly=readonly)}
73 73 ${h.hidden('extern_name', c.extern_name)}
74 74 ${h.hidden('extern_type', c.extern_type)}
75 75 </div>
76 76 </div>
77 77 <div class="field">
78 78 <div class="label">
79 79 <label for="name">${_('First Name')}:</label>
80 80 </div>
81 81 <div class="input">
82 82 ${h.text('firstname', class_="medium")}
83 83 </div>
84 84 </div>
85 85
86 86 <div class="field">
87 87 <div class="label">
88 88 <label for="lastname">${_('Last Name')}:</label>
89 89 </div>
90 90 <div class="input">
91 91 ${h.text('lastname', class_="medium")}
92 92 </div>
93 93 </div>
94 94
95 95 <div class="field">
96 96 <div class="label">
97 97 <label for="email">${_('Email')}:</label>
98 98 </div>
99 99 <div class="input">
100 100 ## we should be able to edit email !
101 101 ${h.text('email', class_="medium")}
102 102 </div>
103 103 </div>
104 104
105 105 <div class="buttons">
106 106 ${h.submit('save', _('Save'), class_="btn")}
107 107 ${h.reset('reset', _('Reset'), class_="btn")}
108 108 </div>
109 109 </div>
110 110 </div>
111 111 % endif
112 112 </div>
113 113 </div> No newline at end of file
@@ -1,67 +1,67 b''
1 1
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Default IP Whitelist For All Users')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="ips_wrap">
9 9 <table class="rctable ip-whitelist">
10 10 <tr>
11 11 <th>IP Address</th>
12 12 <th>IP Range</th>
13 13 <th>Description</th>
14 14 <th></th>
15 15 </tr>
16 16 %if c.user_ip_map:
17 17 %for ip in c.user_ip_map:
18 18 <tr>
19 19 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
20 20 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
21 21 <td class="td-description"><div class="ip">${ip.description}</div></td>
22 22 <td class="td-action">
23 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
23 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
24 24 ${h.hidden('del_ip_id',ip.ip_id)}
25 25 ${h.hidden('default_user', 'True')}
26 26 ${h.submit('remove_',_('Delete'),id="remove_ip_%s" % ip.ip_id,
27 27 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
28 28 ${h.end_form()}
29 29 </td>
30 30 </tr>
31 31 %endfor
32 32 %else:
33 33 <tr>
34 34 <td class="ip">${_('All IP addresses are allowed')}</td>
35 35 <td></td>
36 36 <td></td>
37 37 <td></td>
38 38 </tr>
39 39 %endif
40 40 </table>
41 41 </div>
42 42
43 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
43 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
44 44 <div class="form">
45 45 <!-- fields -->
46 46 <div class="fields">
47 47 <div class="field">
48 48 <div class="label">
49 49 <label for="new_ip">${_('New IP Address')}:</label>
50 50 </div>
51 51 <div class="input">
52 52 ${h.hidden('default_user', 'True')}
53 53 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
54 54 <span class="help-block">${_('Enter a comma separated list of IP Addresses like 127.0.0.1,\n'
55 55 'or use an IP Address with a mask 127.0.0.1/24, to create a network range.\n'
56 56 'To specify multiple addresses in a range, use the 127.0.0.1-127.0.0.10 syntax')}</span>
57 57 </div>
58 58 </div>
59 59 <div class="buttons">
60 60 ${h.submit('save',_('Add'),class_="btn")}
61 61 ${h.reset('reset',_('Reset'),class_="btn")}
62 62 </div>
63 63 </div>
64 64 </div>
65 65 ${h.end_form()}
66 66 </div>
67 67 </div>
@@ -1,210 +1,210 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <%
4 4 elems = [
5 5 (_('Owner'), lambda:base.gravatar_with_user(c.repo_info.user.email), '', ''),
6 6 (_('Created on'), h.format_date(c.repo_info.created_on), '', ''),
7 7 (_('Updated on'), h.format_date(c.repo_info.updated_on), '', ''),
8 8 (_('Cached Commit id'), lambda: h.link_to(c.repo_info.changeset_cache.get('short_id'), h.url('changeset_home',repo_name=c.repo_name,revision=c.repo_info.changeset_cache.get('raw_id'))), '', ''),
9 9 ]
10 10 %>
11 11
12 12 <div class="panel panel-default">
13 13 <div class="panel-heading" id="advanced-info" >
14 14 <h3 class="panel-title">${_('Repository: %s') % c.repo_info.repo_name} <a class="permalink" href="#advanced-info"> ΒΆ</a></h3>
15 15 </div>
16 16 <div class="panel-body">
17 17 ${base.dt_info_panel(elems)}
18 18 </div>
19 19 </div>
20 20
21 21
22 22 <div class="panel panel-default">
23 23 <div class="panel-heading" id="advanced-fork">
24 24 <h3 class="panel-title">${_('Fork Reference')} <a class="permalink" href="#advanced-fork"> ΒΆ</a></h3>
25 25 </div>
26 26 <div class="panel-body">
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST')}
27 ${h.secure_form(h.route_path('edit_repo_advanced_fork', repo_name=c.repo_info.repo_name), method='POST', request=request)}
28 28
29 29 % if c.repo_info.fork:
30 30 <div class="panel-body-title-text">${h.literal(_('This repository is a fork of %(repo_link)s') % {'repo_link': h.link_to_if(c.has_origin_repo_read_perm,c.repo_info.fork.repo_name, h.route_path('repo_summary', repo_name=c.repo_info.fork.repo_name))})}
31 31 | <button class="btn btn-link btn-danger" type="submit">Remove fork reference</button></div>
32 32 % endif
33 33
34 34 <div class="field">
35 35 ${h.hidden('id_fork_of')}
36 36 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('Set'),class_="btn btn-small",)}
37 37 </div>
38 38 <div class="field">
39 39 <span class="help-block">${_('Manually set this repository as a fork of another from the list')}</span>
40 40 </div>
41 41 ${h.end_form()}
42 42 </div>
43 43 </div>
44 44
45 45
46 46 <div class="panel panel-default">
47 47 <div class="panel-heading" id="advanced-journal">
48 48 <h3 class="panel-title">${_('Public Journal Visibility')} <a class="permalink" href="#advanced-journal"> ΒΆ</a></h3>
49 49 </div>
50 50 <div class="panel-body">
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='POST')}
51 ${h.secure_form(h.route_path('edit_repo_advanced_journal', repo_name=c.repo_info.repo_name), method='POST', request=request)}
52 52 <div class="field">
53 53 %if c.in_public_journal:
54 54 <button class="btn btn-small" type="submit">
55 55 ${_('Remove from Public Journal')}
56 56 </button>
57 57 %else:
58 58 <button class="btn btn-small" type="submit">
59 59 ${_('Add to Public Journal')}
60 60 </button>
61 61 %endif
62 62 </div>
63 63 <div class="field" >
64 64 <span class="help-block">${_('All actions made on this repository will be visible to everyone following the public journal.')}</span>
65 65 </div>
66 66 ${h.end_form()}
67 67 </div>
68 68 </div>
69 69
70 70
71 71 <div class="panel panel-default">
72 72 <div class="panel-heading" id="advanced-locking">
73 73 <h3 class="panel-title">${_('Locking state')} <a class="permalink" href="#advanced-locking"> ΒΆ</a></h3>
74 74 </div>
75 75 <div class="panel-body">
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='POST')}
76 ${h.secure_form(h.route_path('edit_repo_advanced_locking', repo_name=c.repo_info.repo_name), method='POST', request=request)}
77 77
78 78 %if c.repo_info.locked[0]:
79 79 <div class="panel-body-title-text">${'Locked by %s on %s. Lock reason: %s' % (h.person_by_id(c.repo_info.locked[0]),
80 80 h.format_date(h. time_to_datetime(c.repo_info.locked[1])), c.repo_info.locked[2])}</div>
81 81 %else:
82 82 <div class="panel-body-title-text">${_('This Repository is not currently locked.')}</div>
83 83 %endif
84 84
85 85 <div class="field" >
86 86 %if c.repo_info.locked[0]:
87 87 ${h.hidden('set_unlock', '1')}
88 88 <button class="btn btn-small" type="submit"
89 89 onclick="return confirm('${_('Confirm to unlock repository.')}');">
90 90 <i class="icon-unlock"></i>
91 91 ${_('Unlock repository')}
92 92 </button>
93 93 %else:
94 94 ${h.hidden('set_lock', '1')}
95 95 <button class="btn btn-small" type="submit"
96 96 onclick="return confirm('${_('Confirm to lock repository.')}');">
97 97 <i class="icon-lock"></i>
98 98 ${_('Lock Repository')}
99 99 </button>
100 100 %endif
101 101 </div>
102 102 <div class="field" >
103 103 <span class="help-block">
104 104 ${_('Force repository locking. This only works when anonymous access is disabled. Pulling from the repository locks the repository to that user until the same user pushes to that repository again.')}
105 105 </span>
106 106 </div>
107 107 ${h.end_form()}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="panel panel-danger">
112 112 <div class="panel-heading" id="advanced-delete">
113 113 <h3 class="panel-title">${_('Delete repository')} <a class="permalink" href="#advanced-delete"> ΒΆ</a></h3>
114 114 </div>
115 115 <div class="panel-body">
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST')}
116 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=c.repo_name), method='POST', request=request)}
117 117 <table class="display">
118 118 <tr>
119 119 <td>
120 120 ${_ungettext('This repository has %s fork.', 'This repository has %s forks.', c.repo_info.forks.count()) % c.repo_info.forks.count()}
121 121 </td>
122 122 <td>
123 123 %if c.repo_info.forks.count():
124 124 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
125 125 %endif
126 126 </td>
127 127 <td>
128 128 %if c.repo_info.forks.count():
129 129 <input type="radio" name="forks" value="delete_forks"/> <label for="forks">${_('Delete forks')}</label>
130 130 %endif
131 131 </td>
132 132 </tr>
133 133 </table>
134 134 <div style="margin: 0 0 20px 0" class="fake-space"></div>
135 135
136 136 <div class="field">
137 137 <button class="btn btn-small btn-danger" type="submit"
138 138 onclick="return confirm('${_('Confirm to delete this repository: %s') % c.repo_name}');">
139 139 <i class="icon-remove-sign"></i>
140 140 ${_('Delete This Repository')}
141 141 </button>
142 142 </div>
143 143 <div class="field">
144 144 <span class="help-block">
145 145 ${_('This repository will be renamed in a special way in order to make it inaccessible to RhodeCode Enterprise and its VCS systems. If you need to fully delete it from the file system, please do it manually, or with rhodecode-cleanup-repos command available in rhodecode-tools.')}
146 146 </span>
147 147 </div>
148 148
149 149 ${h.end_form()}
150 150 </div>
151 151 </div>
152 152
153 153
154 154 <script>
155 155
156 156 var currentRepoId = ${c.repo_info.repo_id};
157 157
158 158 var repoTypeFilter = function(data) {
159 159 var results = [];
160 160
161 161 if (!data.results[0]) {
162 162 return data
163 163 }
164 164
165 165 $.each(data.results[0].children, function() {
166 166 // filter out the SAME repo, it cannot be used as fork of itself
167 167 if (this.obj.repo_id != currentRepoId) {
168 168 this.id = this.obj.repo_id;
169 169 results.push(this)
170 170 }
171 171 });
172 172 data.results[0].children = results;
173 173 return data;
174 174 };
175 175
176 176 $("#id_fork_of").select2({
177 177 cachedDataSource: {},
178 178 minimumInputLength: 2,
179 179 placeholder: "${_('Change repository') if c.repo_info.fork else _('Pick repository')}",
180 180 dropdownAutoWidth: true,
181 181 containerCssClass: "drop-menu",
182 182 dropdownCssClass: "drop-menu-dropdown",
183 183 formatResult: formatResult,
184 184 query: $.debounce(250, function(query){
185 185 self = this;
186 186 var cacheKey = query.term;
187 187 var cachedData = self.cachedDataSource[cacheKey];
188 188
189 189 if (cachedData) {
190 190 query.callback({results: cachedData.results});
191 191 } else {
192 192 $.ajax({
193 193 url: pyroutes.url('repo_list_data'),
194 194 data: {'query': query.term, repo_type: '${c.repo_info.repo_type}'},
195 195 dataType: 'json',
196 196 type: 'GET',
197 197 success: function(data) {
198 198 data = repoTypeFilter(data);
199 199 self.cachedDataSource[cacheKey] = data;
200 200 query.callback({results: data.results});
201 201 },
202 202 error: function(data, textStatus, errorThrown) {
203 203 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
204 204 }
205 205 })
206 206 }
207 207 })
208 208 });
209 209 </script>
210 210
@@ -1,53 +1,53 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Invalidate Cache for Repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6
7 7 <h4>${_('Manually invalidate the repository cache. On the next access a repository cache will be recreated.')}</h4>
8 8
9 9 <p>
10 10 ${_('Cache purge can be automated by such api call. Can be called periodically in crontab etc.')}
11 11 <br/>
12 12 <code>
13 13 ${h.api_call_example(method='invalidate_cache', args={"repoid": c.repo_info.repo_name})}
14 14 </code>
15 15 </p>
16 16
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST')}
17 ${h.secure_form(h.route_path('edit_repo_caches', repo_name=c.repo_name), method='POST', request=request)}
18 18 <div class="form">
19 19 <div class="fields">
20 20 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="btn btn-small",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
21 21 </div>
22 22 </div>
23 23 ${h.end_form()}
24 24
25 25 </div>
26 26 </div>
27 27
28 28
29 29 <div class="panel panel-default">
30 30 <div class="panel-heading">
31 31 <h3 class="panel-title">
32 32 ${(_ungettext('List of repository caches (%(count)s entry)', 'List of repository caches (%(count)s entries)' ,len(c.repo_info.cache_keys)) % {'count': len(c.repo_info.cache_keys)})}
33 33 </h3>
34 34 </div>
35 35 <div class="panel-body">
36 36 <div class="field" >
37 37 <table class="rctable edit_cache">
38 38 <tr>
39 39 <th>${_('Prefix')}</th>
40 40 <th>${_('Key')}</th>
41 41 <th>${_('Active')}</th>
42 42 </tr>
43 43 %for cache in c.repo_info.cache_keys:
44 44 <tr>
45 45 <td class="td-prefix">${cache.get_prefix() or '-'}</td>
46 46 <td class="td-cachekey">${cache.cache_key}</td>
47 47 <td class="td-active">${h.bool2icon(cache.cache_active)}</td>
48 48 </tr>
49 49 %endfor
50 50 </table>
51 51 </div>
52 52 </div>
53 53 </div>
@@ -1,123 +1,123 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Repository Permissions')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST')}
8 ${h.secure_form(h.route_path('edit_repo_perms', repo_name=c.repo_name), method='POST', request=request)}
9 9 <table id="permissions_manage" class="rctable permissions">
10 10 <tr>
11 11 <th class="td-radio">${_('None')}</th>
12 12 <th class="td-radio">${_('Read')}</th>
13 13 <th class="td-radio">${_('Write')}</th>
14 14 <th class="td-radio">${_('Admin')}</th>
15 15 <th class="td-owner">${_('User/User Group')}</th>
16 16 <th></th>
17 17 </tr>
18 18 ## USERS
19 19 %for _user in c.repo_info.permissions():
20 20 %if getattr(_user, 'admin_row', None) or getattr(_user, 'owner_row', None):
21 21 <tr class="perm_admin_row">
22 22 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.none', disabled="disabled")}</td>
23 23 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.read', disabled="disabled")}</td>
24 24 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.write', disabled="disabled")}</td>
25 25 <td class="td-radio">${h.radio('admin_perm_%s' % _user.user_id,'repository.admin', 'repository.admin', disabled="disabled")}</td>
26 26 <td class="td-user">
27 27 ${base.gravatar(_user.email, 16)}
28 28 ${h.link_to_user(_user.username)}
29 29 %if getattr(_user, 'admin_row', None):
30 30 (${_('super admin')})
31 31 %endif
32 32 %if getattr(_user, 'owner_row', None):
33 33 (${_('owner')})
34 34 %endif
35 35 </td>
36 36 <td></td>
37 37 </tr>
38 38 %elif _user.username == h.DEFAULT_USER and c.repo_info.private:
39 39 <tr>
40 40 <td colspan="4">
41 41 <span class="private_repo_msg">
42 42 <strong title="${h.tooltip(_user.permission)}">${_('private repository')}</strong>
43 43 </span>
44 44 </td>
45 45 <td class="private_repo_msg">
46 46 ${base.gravatar(h.DEFAULT_USER_EMAIL, 16)}
47 47 ${h.DEFAULT_USER} - ${_('only users/user groups explicitly added here will have access')}</td>
48 48 <td></td>
49 49 </tr>
50 50 %else:
51 51 <tr>
52 52 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.none', checked=_user.permission=='repository.none')}</td>
53 53 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.read', checked=_user.permission=='repository.read')}</td>
54 54 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.write', checked=_user.permission=='repository.write')}</td>
55 55 <td class="td-radio">${h.radio('u_perm_%s' % _user.user_id,'repository.admin', checked=_user.permission=='repository.admin')}</td>
56 56 <td class="td-user">
57 57 ${base.gravatar(_user.email, 16)}
58 58 <span class="user">
59 59 % if _user.username == h.DEFAULT_USER:
60 60 ${h.DEFAULT_USER} <span class="user-perm-help-text"> - ${_('permission for all other users')}</span>
61 61 % else:
62 62 ${h.link_to_user(_user.username)}
63 63 % endif
64 64 </span>
65 65 </td>
66 66 <td class="td-action">
67 67 %if _user.username != h.DEFAULT_USER:
68 68 <span class="btn btn-link btn-danger revoke_perm"
69 69 member="${_user.user_id}" member_type="user">
70 70 <i class="icon-remove"></i> ${_('Revoke')}
71 71 </span>
72 72 %endif
73 73 </td>
74 74 </tr>
75 75 %endif
76 76 %endfor
77 77
78 78 ## USER GROUPS
79 79 %for _user_group in c.repo_info.permission_user_groups():
80 80 <tr>
81 81 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.none', checked=_user_group.permission=='repository.none')}</td>
82 82 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.read', checked=_user_group.permission=='repository.read')}</td>
83 83 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.write', checked=_user_group.permission=='repository.write')}</td>
84 84 <td class="td-radio">${h.radio('g_perm_%s' % _user_group.users_group_id,'repository.admin', checked=_user_group.permission=='repository.admin')}</td>
85 85 <td class="td-componentname">
86 86 <i class="icon-group" ></i>
87 87 %if h.HasPermissionAny('hg.admin')():
88 88 <a href="${h.url('edit_users_group',user_group_id=_user_group.users_group_id)}">
89 89 ${_user_group.users_group_name}
90 90 </a>
91 91 %else:
92 92 ${_user_group.users_group_name}
93 93 %endif
94 94 </td>
95 95 <td class="td-action">
96 96 <span class="btn btn-link btn-danger revoke_perm"
97 97 member="${_user_group.users_group_id}" member_type="user_group">
98 98 <i class="icon-remove"></i> ${_('Revoke')}
99 99 </span>
100 100 </td>
101 101 </tr>
102 102 %endfor
103 103 <tr class="new_members" id="add_perm_input"></tr>
104 104 </table>
105 105 <div id="add_perm" class="link">
106 106 ${_('Add new')}
107 107 </div>
108 108 <div class="buttons">
109 109 ${h.submit('save',_('Save'),class_="btn btn-primary")}
110 110 ${h.reset('reset',_('Reset'),class_="btn btn-danger")}
111 111 </div>
112 112 ${h.end_form()}
113 113 </div>
114 114 </div>
115 115
116 116 <script type="text/javascript">
117 117 $('#add_perm').on('click', function(e){
118 118 addNewPermInput($(this), 'repository');
119 119 });
120 120 $('.revoke_perm').on('click', function(e){
121 121 markRevokePermInput($(this), 'repository');
122 122 });
123 123 </script>
@@ -1,260 +1,260 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4 <div class="panel panel-default">
5 5 <div class="panel-heading">
6 6 <h3 class="panel-title">${_('Settings for Repository: %s') % c.rhodecode_db_repo.repo_name}</h3>
7 7 </div>
8 8 <div class="panel-body">
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST')}
9 ${h.secure_form(h.route_path('edit_repo', repo_name=c.rhodecode_db_repo.repo_name), method='POST', request=request)}
10 10 <div class="form">
11 11 <!-- fields -->
12 12 <div class="fields">
13 13
14 14 <div class="field">
15 15 <div class="label">
16 16 <label for="repo_name">${_('Name')}:</label>
17 17 </div>
18 18 <div class="input">
19 19 ${c.form['repo_name'].render(css_class='medium', oid='repo_name')|n}
20 20 ${c.form.render_error(request, c.form['repo_name'])|n}
21 21
22 22 <p class="help-block">${_('Non-changeable id')}: `_${c.rhodecode_db_repo.repo_id}` <span><a href="#" onclick="$('#clone_id').toggle();return false">${_('what is that ?')}</a></span></p>
23 23 <p id="clone_id" style="display:none;">
24 24 ${_('URL by id')}: `${c.rhodecode_db_repo.clone_url(with_id=True)}` <br/>
25 25 ${_('''In case this repository is renamed or moved into another group the repository url changes.
26 26 Using above url guarantees that this repository will always be accessible under such url.
27 27 Useful for CI systems, or any other cases that you need to hardcode the url into 3rd party service.''')}</p>
28 28 </div>
29 29 </div>
30 30
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="repo_group">${_('Repository group')}:</label>
34 34 </div>
35 35 <div class="select">
36 36 ${c.form['repo_group'].render(css_class='medium', oid='repo_group')|n}
37 37 ${c.form.render_error(request, c.form['repo_group'])|n}
38 38
39 39 % if c.personal_repo_group:
40 40 <a class="btn" href="#" data-personal-group-name="${c.personal_repo_group.group_name}" data-personal-group-id="${c.personal_repo_group.group_id}" onclick="selectMyGroup(this); return false">
41 41 ${_('Select my personal group (`%(repo_group_name)s`)') % {'repo_group_name': c.personal_repo_group.group_name}}
42 42 </a>
43 43 % endif
44 44 <p class="help-block">${_('Optional select a group to put this repository into.')}</p>
45 45 </div>
46 46 </div>
47 47
48 48 % if c.rhodecode_db_repo.repo_type != 'svn':
49 49 <div class="field">
50 50 <div class="label">
51 51 <label for="clone_uri">${_('Remote uri')}:</label>
52 52 </div>
53 53 <div class="input">
54 54 %if c.rhodecode_db_repo.clone_uri:
55 55 ## display, if we don't have any errors
56 56 % if not c.form['repo_clone_uri'].error:
57 57 <div id="clone_uri_hidden" class='text-as-placeholder'>
58 58 <span id="clone_uri_hidden_value">${c.rhodecode_db_repo.clone_uri_hidden}</span>
59 59 <span class="link" id="edit_clone_uri"><i class="icon-edit"></i>${_('edit')}</span>
60 60 </div>
61 61 % endif
62 62
63 63 ## alter field
64 64 <div id="alter_clone_uri" style="${'' if c.form['repo_clone_uri'].error else 'display: none'}">
65 65 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri', placeholder=_('enter new value, or leave empty to remove'))|n}
66 66 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
67 67 % if c.form['repo_clone_uri'].error:
68 68 ## we got error from form subit, means we modify the url
69 69 ${h.hidden('repo_clone_uri_change', 'MOD')}
70 70 % else:
71 71 ${h.hidden('repo_clone_uri_change', 'OLD')}
72 72 % endif
73 73
74 74 % if not c.form['repo_clone_uri'].error:
75 75 <span class="link" id="cancel_edit_clone_uri">${_('cancel')}</span>
76 76 % endif
77 77
78 78 </div>
79 79 %else:
80 80 ## not set yet, display form to set it
81 81 ${c.form['repo_clone_uri'].render(css_class='medium', oid='clone_uri')|n}
82 82 ${c.form.render_error(request, c.form['repo_clone_uri'])|n}
83 83 ${h.hidden('repo_clone_uri_change', 'NEW')}
84 84 %endif
85 85 <p id="alter_clone_uri_help_block" class="help-block">
86 86 <% pull_link = h.literal(h.link_to('remote sync', h.url('edit_repo_remote', repo_name=c.repo_name))) %>
87 87 ${_('http[s] url where from repository was imported, this field can used for doing {pull_link}.').format(pull_link=pull_link)|n} <br/>
88 88 ${_('This field is stored encrypted inside Database, a format of http://user:password@server.com/repo_name can be used and will be hidden from display.')}
89 89 </p>
90 90 </div>
91 91 </div>
92 92 % else:
93 93 ${h.hidden('repo_clone_uri', '')}
94 94 % endif
95 95
96 96 <div class="field">
97 97 <div class="label">
98 98 <label for="repo_landing_commit_ref">${_('Landing commit')}:</label>
99 99 </div>
100 100 <div class="select">
101 101 ${c.form['repo_landing_commit_ref'].render(css_class='medium', oid='repo_landing_commit_ref')|n}
102 102 ${c.form.render_error(request, c.form['repo_landing_commit_ref'])|n}
103 103 <p class="help-block">${_('Default commit for files page, downloads, full text search index and readme')}</p>
104 104 </div>
105 105 </div>
106 106
107 107 <div class="field badged-field">
108 108 <div class="label">
109 109 <label for="repo_owner">${_('Owner')}:</label>
110 110 </div>
111 111 <div class="input">
112 112 <div class="badge-input-container">
113 113 <div class="user-badge">
114 114 ${base.gravatar_with_user(c.rhodecode_db_repo.user.email or c.rhodecode_db_repo.user.username, show_disabled=not c.rhodecode_db_repo.user.active)}
115 115 </div>
116 116 <div class="badge-input-wrap">
117 117 ${c.form['repo_owner'].render(css_class='medium', oid='repo_owner')|n}
118 118 </div>
119 119 </div>
120 120 ${c.form.render_error(request, c.form['repo_owner'])|n}
121 121 <p class="help-block">${_('Change owner of this repository.')}</p>
122 122 </div>
123 123 </div>
124 124
125 125 <div class="field">
126 126 <div class="label label-textarea">
127 127 <label for="repo_description">${_('Description')}:</label>
128 128 </div>
129 129 <div class="textarea text-area editor">
130 130 ${c.form['repo_description'].render(css_class='medium', oid='repo_description')|n}
131 131 ${c.form.render_error(request, c.form['repo_description'])|n}
132 132 <p class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</p>
133 133 </div>
134 134 </div>
135 135
136 136 <div class="field">
137 137 <div class="label label-checkbox">
138 138 <label for="${c.form['repo_private'].oid}">${_('Private repository')}:</label>
139 139 </div>
140 140 <div class="checkboxes">
141 141 ${c.form['repo_private'].render(css_class='medium')|n}
142 142 ${c.form.render_error(request, c.form['repo_private'])|n}
143 143 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
144 144 </div>
145 145 </div>
146 146 <div class="field">
147 147 <div class="label label-checkbox">
148 148 <label for="${c.form['repo_enable_statistics'].oid}">${_('Enable statistics')}:</label>
149 149 </div>
150 150 <div class="checkboxes">
151 151 ${c.form['repo_enable_statistics'].render(css_class='medium')|n}
152 152 ${c.form.render_error(request, c.form['repo_enable_statistics'])|n}
153 153 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
154 154 </div>
155 155 </div>
156 156 <div class="field">
157 157 <div class="label label-checkbox">
158 158 <label for="${c.form['repo_enable_downloads'].oid}">${_('Enable downloads')}:</label>
159 159 </div>
160 160 <div class="checkboxes">
161 161 ${c.form['repo_enable_downloads'].render(css_class='medium')|n}
162 162 ${c.form.render_error(request, c.form['repo_enable_downloads'])|n}
163 163 <span class="help-block">${_('Enable download menu on summary page.')}</span>
164 164 </div>
165 165 </div>
166 166 <div class="field">
167 167 <div class="label label-checkbox">
168 168 <label for="${c.form['repo_enable_locking'].oid}">${_('Enable automatic locking')}:</label>
169 169 </div>
170 170 <div class="checkboxes">
171 171 ${c.form['repo_enable_locking'].render(css_class='medium')|n}
172 172 ${c.form.render_error(request, c.form['repo_enable_locking'])|n}
173 173 <span class="help-block">${_('Enable automatic locking on repository. Pulling from this repository creates a lock that can be released by pushing back by the same user')}</span>
174 174 </div>
175 175 </div>
176 176
177 177 %if c.visual.repository_fields:
178 178 ## EXTRA FIELDS
179 179 %for field in c.repo_fields:
180 180 <div class="field">
181 181 <div class="label">
182 182 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
183 183 </div>
184 184 <div class="input input-medium">
185 185 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
186 186 %if field.field_desc:
187 187 <span class="help-block">${field.field_desc}</span>
188 188 %endif
189 189 </div>
190 190 </div>
191 191 %endfor
192 192 %endif
193 193 <div class="buttons">
194 194 ${h.submit('save',_('Save'),class_="btn")}
195 195 ${h.reset('reset',_('Reset'),class_="btn")}
196 196 </div>
197 197 </div>
198 198 </div>
199 199 ${h.end_form()}
200 200 </div>
201 201 </div>
202 202
203 203 <script>
204 204 $(document).ready(function(){
205 205 var cloneUrl = function() {
206 206 var alterButton = $('#alter_clone_uri');
207 207 var editButton = $('#edit_clone_uri');
208 208 var cancelEditButton = $('#cancel_edit_clone_uri');
209 209 var hiddenUrl = $('#clone_uri_hidden');
210 210 var hiddenUrlValue = $('#clone_uri_hidden_value');
211 211 var input = $('#clone_uri');
212 212 var helpBlock = $('#alter_clone_uri_help_block');
213 213 var changedFlag = $('#repo_clone_uri_change');
214 214 var originalText = helpBlock.html();
215 215 var obfuscatedUrl = hiddenUrlValue.html();
216 216
217 217 var edit = function(e) {
218 218 alterButton.show();
219 219 editButton.hide();
220 220 hiddenUrl.hide();
221 221
222 222 //add the old value next to input for verification
223 223 helpBlock.html("(" + obfuscatedUrl + ")" + "<br\>" + originalText);
224 224 changedFlag.val('MOD');
225 225 };
226 226
227 227 var cancelEdit = function(e) {
228 228 alterButton.hide();
229 229 editButton.show();
230 230 hiddenUrl.show();
231 231
232 232 helpBlock.html(originalText);
233 233 changedFlag.val('OLD');
234 234 input.val('');
235 235 };
236 236
237 237 var initEvents = function() {
238 238 editButton.on('click', edit);
239 239 cancelEditButton.on('click', cancelEdit);
240 240 };
241 241
242 242 var setInitialState = function() {
243 243 if (input.hasClass('error')) {
244 244 alterButton.show();
245 245 editButton.hide();
246 246 hiddenUrl.hide();
247 247 }
248 248 };
249 249
250 250 setInitialState();
251 251 initEvents();
252 252 }();
253 253
254 254 selectMyGroup = function(element) {
255 255 $("#repo_group").val($(element).data('personalGroupId')).trigger("change");
256 256 };
257 257
258 258 UsersAutoComplete('repo_owner', '${c.rhodecode_user.user_id}');
259 259 });
260 260 </script>
@@ -1,197 +1,197 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Strip commits from repository')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 %if c.repo_info.repo_type != 'svn':
7 7 <h4>${_('Please provide up to %d commits commits to strip') % c.strip_limit}</h4>
8 8 <p>
9 9 ${_('In the first step commits will be verified for existance in the repository')}. </br>
10 10 ${_('In the second step, correct commits will be available for stripping')}.
11 11 </p>
12 ${h.secure_form(h.route_path('strip_check', repo_name=c.repo_info.repo_name), method='post')}
12 ${h.secure_form(h.route_path('strip_check', repo_name=c.repo_info.repo_name), method='POST', request=request)}
13 13 <div id="change_body" class="field">
14 14 <div id="box-1" class="inputx locked_input">
15 15 <input class="text" id="changeset_id-1" name="changeset_id-1" size="59"
16 16 placeholder="${_('Enter full 40 character commit sha')}" type="text" value="">
17 17 <div id="plus_icon-1" class="btn btn-default plus_input_button" onclick="addNew(1);return false">
18 18 <i class="icon-plus">${_('Add another commit')}</i>
19 19 </div>
20 20 </div>
21 21 </div>
22 22
23 23 <div id="results" style="display:none; padding: 10px 0px;"></div>
24 24
25 25 <div class="buttons">
26 26 <button id="strip_action" class="btn btn-small btn-primary" onclick="checkCommits();return false">
27 27 ${_('Check commits')}
28 28 </button>
29 29 </div>
30 30
31 31 ${h.end_form()}
32 32 %else:
33 33 <h4>${_('Sorry this functionality is not available for SVN repository')}</h4>
34 34 %endif
35 35 </div>
36 36 </div>
37 37
38 38
39 39 <script>
40 40 var plus_leaf = 1;
41 41
42 42 addNew = function(number){
43 43 if (number >= ${c.strip_limit}){
44 44 return;
45 45 }
46 46 var minus = '<i class="icon-minus">${_('Remove')}</i>';
47 47 $('#plus_icon-'+number).detach();
48 48 number++;
49 49
50 50 var input = '<div id="box-'+number+'" class="inputx locked_input">'+
51 51 '<input class="text" id="changeset_id-'+number+'" name="changeset_id-'+number+'" size="59" type="text" value=""' +
52 52 'placeholder="${_('Enter full 40 character commit sha')}">'+
53 53 '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number+');return false">'+
54 54 '<i class="icon-plus">${_('Add another commit')}</i>'+
55 55 '</div>'+
56 56 '<div id="minus_icon-'+number+'" class="btn btn-default minus_input_button" onclick="delOld('+(number)+');return false">'+
57 57 minus +
58 58 '</div>' +
59 59 '</div>';
60 60 $('#change_body').append(input);
61 61 plus_leaf++;
62 62 };
63 63
64 64 reIndex = function(number){
65 65 for(var i=number;i<=plus_leaf;i++){
66 66 var check = $('#box-'+i);
67 67 if (check.length == 0){
68 68 var change = $('#box-'+(i+1));
69 69 change.attr('id','box-'+i);
70 70 var plus = $('#plus_icon-'+(i+1));
71 71
72 72 if (plus.length != 0){
73 73 plus.attr('id','plus_icon-'+i);
74 74 plus.attr('onclick','addNew('+i+');return false');
75 75 plus_leaf--;
76 76 }
77 77 var minus = $('#minus_icon-'+(i+1));
78 78
79 79 minus.attr('id','minus_icon-'+i);
80 80
81 81 minus.attr('onclick','delOld('+i+');re' +
82 82 'turn false');
83 83 var input = $('input#changeset_id-'+(i+1));
84 84 input.attr('name','changeset_id-'+i);
85 85 input.attr('id','changeset_id-'+i);
86 86 }
87 87 }
88 88 };
89 89
90 90 delOld = function(number){
91 91 $('#box-'+number).remove();
92 92 number = number - 1;
93 93 var box = $('#box-'+number);
94 94 var plus = '<div id="plus_icon-'+number+'" class="btn btn-default plus_input_button" onclick="addNew('+number +');return false">'+
95 95 '<i id="i_plus_icon-'+number+'" class="icon-plus">${_('Add another commit')}</i></div>';
96 96 var minus = $('#minus_icon-'+number);
97 97 if(number +1 == plus_leaf){
98 98 minus.detach();
99 99 box.append(plus);
100 100 box.append(minus);
101 101 plus_leaf --;
102 102 }
103 103 reIndex(number+1);
104 104
105 105 };
106 106
107 107 var resultData = {
108 108 'csrf_token': CSRF_TOKEN
109 109 };
110 110
111 111 checkCommits = function() {
112 112 var postData = $('form').serialize();
113 113 $('#results').show();
114 114 $('#results').html('<h4>${_('Checking commits')}...</h4>');
115 115 var url = "${h.route_path('strip_check', repo_name=c.repo_info.repo_name)}";
116 116 var btn = $('#strip_action');
117 117 btn.attr('disabled', 'disabled');
118 118 btn.addClass('disabled');
119 119
120 120 var success = function (data) {
121 121 resultData = {
122 122 'csrf_token': CSRF_TOKEN
123 123 };
124 124 var i = 0;
125 125 var result = '<ol>';
126 126 $.each(data, function(index, value){
127 127 i= index;
128 128 var box = $('#box-'+index);
129 129 if (value.rev){
130 130 resultData[index] = JSON.stringify(value);
131 131
132 132 var verifiedHtml = (
133 133 '<li style="line-height:1.2em">' +
134 134 '<code>{0}</code>' +
135 135 '{1}' +
136 136 '<div style="white-space:pre">' +
137 137 'author: {2}\n' +
138 138 'description: {3}' +
139 139 '</div>' +
140 140 '</li>').format(
141 141 value.rev,
142 142 "${_(' commit verified positive')}",
143 143 value.author, value.comment
144 144 );
145 145 result += verifiedHtml;
146 146 }
147 147 else {
148 148 var verifiedHtml = (
149 149 '<li style="line-height:1.2em">' +
150 150 '<code><strike>{0}</strike></code>' +
151 151 '{1}' +
152 152 '</li>').format(
153 153 value.commit,
154 154 "${_(' commit verified negative')}"
155 155 );
156 156 result += verifiedHtml;
157 157 }
158 158 box.remove();
159 159 });
160 160 result += '</ol>';
161 161 var box = $('#box-'+(parseInt(i)+1));
162 162 box.remove();
163 163 $('#results').html(result);
164 164 };
165 165
166 166 btn.html('Strip');
167 167 btn.removeAttr('disabled');
168 168 btn.removeClass('disabled');
169 169 btn.attr('onclick','strip();return false;');
170 170 ajaxPOST(url, postData, success, null);
171 171 };
172 172
173 173 strip = function() {
174 174 var url = "${h.route_path('strip_execute', repo_name=c.repo_info.repo_name)}";
175 175 var success = function(data) {
176 176 var result = '<h4>Strip executed</h4><ol>';
177 177 $.each(data, function(index, value){
178 178 if(data[index]) {
179 179 result += '<li><code>' +index+ '</code> ${_(' commit striped successfully')}' + '</li>';
180 180 }
181 181 else {
182 182 result += '<li><code>' +index+ '</code> ${_(' commit strip failed')}' + '</li>';
183 183 }
184 184 });
185 185 if ($.isEmptyObject(data)) {
186 186 result += '<li>Nothing done...</li>'
187 187 }
188 188 result += '</ol>';
189 189 $('#results').html(result);
190 190
191 191 };
192 192 ajaxPOST(url, resultData, success, null);
193 193 var btn = $('#strip_action');
194 194 btn.remove();
195 195
196 196 };
197 197 </script>
@@ -1,62 +1,62 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('User Sessions Configuration')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <%
7 7 elems = [
8 8 (_('Session type'), c.session_model.SESSION_TYPE, ''),
9 9 (_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
10 10
11 11 (_('Total sessions'), c.session_count, ''),
12 12 (_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),
13 13
14 14 ]
15 15 %>
16 16 <dl class="dl-horizontal settings">
17 17 %for dt, dd, tt in elems:
18 18 <dt>${dt}:</dt>
19 19 <dd title="${h.tooltip(tt)}">${dd}</dd>
20 20 %endfor
21 21 </dl>
22 22 </div>
23 23 </div>
24 24
25 25
26 26 <div class="panel panel-warning">
27 27 <div class="panel-heading">
28 28 <h3 class="panel-title">${_('Cleanup Old Sessions')}</h3>
29 29 </div>
30 30 <div class="panel-body">
31 ${h.secure_form(h.route_path('admin_settings_sessions_cleanup'), method='post')}
31 ${h.secure_form(h.route_path('admin_settings_sessions_cleanup'), method='POST', request=request)}
32 32
33 33 <p>
34 34 ${_('Cleanup user sessions that were not active during chosen time frame.')} <br/>
35 35 ${_('After performing this action users whose session will be removed will be required to log in again.')} <br/>
36 36 <strong>${_('Picking `All` will log-out you, and all users in the system.')}</strong>
37 37 </p>
38 38
39 39 <script type="text/javascript">
40 40 $(document).ready(function() {
41 41 $('#expire_days').select2({
42 42 containerCssClass: 'drop-menu',
43 43 dropdownCssClass: 'drop-menu-dropdown',
44 44 dropdownAutoWidth: true,
45 45 minimumResultsForSearch: -1
46 46 });
47 47 });
48 48 </script>
49 49 <select id="expire_days" name="expire_days">
50 50 % for n in [60, 90, 30, 7, 0]:
51 51 <option value="${n}">${'{} days'.format(n) if n != 0 else 'All'}</option>
52 52 % endfor
53 53 </select>
54 54 <button class="btn btn-small" type="submit"
55 55 onclick="return confirm('${_('Confirm to cleanup user sessions')}');">
56 56 ${_('Cleanup sessions')}
57 57 </button>
58 58 ${h.end_form()}
59 59 </div>
60 60 </div>
61 61
62 62
@@ -1,157 +1,157 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="apikeys_wrap">
7 7 <p>
8 8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 10 </p>
11 11 <table class="rctable auth_tokens">
12 12 <tr>
13 13 <th>${_('Token')}</th>
14 14 <th>${_('Scope')}</th>
15 15 <th>${_('Description')}</th>
16 16 <th>${_('Role')}</th>
17 17 <th>${_('Expiration')}</th>
18 18 <th>${_('Action')}</th>
19 19 </tr>
20 20 %if c.user_auth_tokens:
21 21 %for auth_token in c.user_auth_tokens:
22 22 <tr class="${'expired' if auth_token.expired else ''}">
23 23 <td class="truncate-wrap td-authtoken"><div class="user_auth_tokens truncate autoexpand"><code>${auth_token.api_key}</code></div></td>
24 24 <td class="td">${auth_token.scope_humanized}</td>
25 25 <td class="td-wrap">${auth_token.description}</td>
26 26 <td class="td-tags">
27 27 <span class="tag disabled">${auth_token.role_humanized}</span>
28 28 </td>
29 29 <td class="td-exp">
30 30 %if auth_token.expires == -1:
31 31 ${_('never')}
32 32 %else:
33 33 %if auth_token.expired:
34 34 <span style="text-decoration: line-through">${h.age_component(h.time_to_utcdatetime(auth_token.expires))}</span>
35 35 %else:
36 36 ${h.age_component(h.time_to_utcdatetime(auth_token.expires))}
37 37 %endif
38 38 %endif
39 39 </td>
40 40 <td class="td-action">
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='POST')}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='POST', request=request)}
42 42 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
43 43 <button class="btn btn-link btn-danger" type="submit"
44 44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
45 45 ${_('Delete')}
46 46 </button>
47 47 ${h.end_form()}
48 48 </td>
49 49 </tr>
50 50 %endfor
51 51 %else:
52 52 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
53 53 %endif
54 54 </table>
55 55 </div>
56 56
57 57 <div class="user_auth_tokens">
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='POST')}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='POST', request=request)}
59 59 <div class="form form-vertical">
60 60 <!-- fields -->
61 61 <div class="fields">
62 62 <div class="field">
63 63 <div class="label">
64 64 <label for="new_email">${_('New authentication token')}:</label>
65 65 </div>
66 66 <div class="input">
67 67 ${h.text('description', class_='medium', placeholder=_('Description'))}
68 68 ${h.select('lifetime', '', c.lifetime_options)}
69 69 ${h.select('role', '', c.role_options)}
70 70
71 71 % if c.allow_scoped_tokens:
72 72 ${h.hidden('scope_repo_id')}
73 73 % else:
74 74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 75 % endif
76 76 </div>
77 77 <p class="help-block">
78 78 ${_('Repository scope works only with tokens with VCS type.')}
79 79 </p>
80 80 </div>
81 81 <div class="buttons">
82 82 ${h.submit('save',_('Add'),class_="btn")}
83 83 ${h.reset('reset',_('Reset'),class_="btn")}
84 84 </div>
85 85 </div>
86 86 </div>
87 87 ${h.end_form()}
88 88 </div>
89 89 </div>
90 90 </div>
91 91
92 92 <script>
93 93
94 94 $(document).ready(function(){
95 95 var select2Options = {
96 96 'containerCssClass': "drop-menu",
97 97 'dropdownCssClass': "drop-menu-dropdown",
98 98 'dropdownAutoWidth': true
99 99 };
100 100 $("#lifetime").select2(select2Options);
101 101 $("#role").select2(select2Options);
102 102
103 103 var repoFilter = function(data) {
104 104 var results = [];
105 105
106 106 if (!data.results[0]) {
107 107 return data
108 108 }
109 109
110 110 $.each(data.results[0].children, function() {
111 111 // replace name to ID for submision
112 112 this.id = this.obj.repo_id;
113 113 results.push(this);
114 114 });
115 115
116 116 data.results[0].children = results;
117 117 return data;
118 118 };
119 119
120 120 $("#scope_repo_id_disabled").select2(select2Options);
121 121
122 122 $("#scope_repo_id").select2({
123 123 cachedDataSource: {},
124 124 minimumInputLength: 2,
125 125 placeholder: "${_('repository scope')}",
126 126 dropdownAutoWidth: true,
127 127 containerCssClass: "drop-menu",
128 128 dropdownCssClass: "drop-menu-dropdown",
129 129 formatResult: formatResult,
130 130 query: $.debounce(250, function(query){
131 131 self = this;
132 132 var cacheKey = query.term;
133 133 var cachedData = self.cachedDataSource[cacheKey];
134 134
135 135 if (cachedData) {
136 136 query.callback({results: cachedData.results});
137 137 } else {
138 138 $.ajax({
139 139 url: pyroutes.url('repo_list_data'),
140 140 data: {'query': query.term},
141 141 dataType: 'json',
142 142 type: 'GET',
143 143 success: function(data) {
144 144 data = repoFilter(data);
145 145 self.cachedDataSource[cacheKey] = data;
146 146 query.callback({results: data.results});
147 147 },
148 148 error: function(data, textStatus, errorThrown) {
149 149 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
150 150 }
151 151 })
152 152 }
153 153 })
154 154 });
155 155
156 156 });
157 157 </script>
@@ -1,71 +1,71 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Additional Email Addresses')}</h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="emails_wrap">
9 9 <table class="rctable account_emails useremails">
10 10 <tr>
11 11 <td class="td-user">
12 12 ${base.gravatar(c.user.email, 16)}
13 13 <span class="user email">${c.user.email}</span>
14 14 </td>
15 15 <td class="td-tags">
16 16 <span class="tag">${_('Primary')}</span>
17 17 </td>
18 18 </tr>
19 19 %if c.user_email_map:
20 20 %for em in c.user_email_map:
21 21 <tr>
22 22 <td class="td-user">
23 23 ${base.gravatar(em.email, 16)}
24 24 <span class="user email">${em.email}</span>
25 25 </td>
26 26 <td class="td-action">
27 ${h.secure_form(h.route_path('edit_user_emails_delete', user_id=c.user.user_id), method='POST')}
27 ${h.secure_form(h.route_path('edit_user_emails_delete', user_id=c.user.user_id), method='POST', request=request)}
28 28 ${h.hidden('del_email_id', em.email_id)}
29 29 <button class="btn btn-link btn-danger" type="submit"
30 30 onclick="return confirm('${_('Confirm to delete this email: %s') % em.email}');">
31 31 ${_('Delete')}
32 32 </button>
33 33 ${h.end_form()}
34 34 </td>
35 35 </tr>
36 36 %endfor
37 37 %else:
38 38 <tr class="noborder">
39 39 <td colspan="3">
40 40 <div class="td-email">
41 41 ${_('No additional emails specified')}
42 42 </div>
43 43 </td>
44 44 </tr>
45 45 %endif
46 46 </table>
47 47 </div>
48 48
49 ${h.secure_form(h.route_path('edit_user_emails_add', user_id=c.user.user_id), method='POST')}
49 ${h.secure_form(h.route_path('edit_user_emails_add', user_id=c.user.user_id), method='POST', request=request)}
50 50 <div class="form">
51 51 <!-- fields -->
52 52 <div class="fields">
53 53 <div class="field">
54 54 <div class="label">
55 55 <label for="new_email">${_('New email address')}:</label>
56 56 </div>
57 57 <div class="input">
58 58 ${h.text('new_email', class_='medium')}
59 59 </div>
60 60 </div>
61 61 <div class="buttons">
62 62 ${h.submit('save',_('Add'),class_="btn btn-small")}
63 63 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
64 64 </div>
65 65 </div>
66 66 </div>
67 67 ${h.end_form()}
68 68 </div>
69 69 </div>
70 70
71 71
@@ -1,147 +1,147 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3
4 4 <div class="panel panel-default">
5 5 <div class="panel-heading">
6 6 <h3 class="panel-title">${_('User groups administration')}</h3>
7 7 </div>
8 8 <div class="panel-body">
9 9 <div class="fields">
10 10 <div class="field">
11 11 <div class="label label-checkbox">
12 12 <label for="users_group_active">${_('Add `%s` to user group') % c.user.username}:</label>
13 13 </div>
14 14 <div class="input">
15 15 ${h.text('add_user_to_group', placeholder="user group name", class_="medium")}
16 16 </div>
17 17
18 18 </div>
19 19 </div>
20 20
21 21 <div class="groups_management">
22 ${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='post')}
22 ${h.secure_form(h.route_path('edit_user_groups_management_updates', user_id=c.user.user_id), method='POST', request=request)}
23 23 <div id="repos_list_wrap">
24 24 <table id="user_group_list_table" class="display"></table>
25 25 </div>
26 26 <div class="buttons">
27 27 ${h.submit('save',_('Save'),class_="btn")}
28 28 </div>
29 29 ${h.end_form()}
30 30 </div>
31 31 </div>
32 32 </div>
33 33 <script>
34 34 var api;
35 35 $(document).ready(function() {
36 36
37 37 var get_datatable_count = function(){
38 38 $('#user_group_count').text(api.page.info().recordsDisplay);
39 39 };
40 40
41 41 $('#user_group_list_table').on('click', 'a.editor_remove', function (e) {
42 42 e.preventDefault();
43 43 var row = api.row($(this).closest('tr'));
44 44 row.remove().draw();
45 45 } );
46 46
47 47 $('#user_group_list_table').DataTable({
48 48 data: ${c.groups|n},
49 49 dom: 'rtp',
50 50 pageLength: ${c.visual.admin_grid_items},
51 51 order: [[ 0, "asc" ]],
52 52 columns: [
53 53 { data: {"_": "group_name",
54 54 "sort": "group_name"}, title: "${_('Name')}", className: "td-componentname," ,
55 55 render: function (data,type,full,meta)
56 56 {return '<div><i class="icon-group" title="User group">'+data+'</i></div>'}},
57 57
58 58 { data: {"_": "group_description",
59 59 "sort": "group_description"}, title: "${_('Description')}", className: "td-description" },
60 60 { data: {"_": "users_group_id"}, className: "td-user",
61 61 render: function (data,type,full,meta)
62 62 {return '<input type="hidden" name="users_group_id" value="'+data+'">'}},
63 63 { data: {"_": "active",
64 64 "sort": "active"}, title: "${_('Active')}", className: "td-active", className: "td-number"},
65 65 { data: {"_": "owner_data"}, title: "${_('Owner')}", className: "td-user",
66 66 render: function (data,type,full,meta)
67 67 {return '<div class="rc-user tooltip">'+
68 68 '<img class="gravatar" src="'+ data.owner_icon +'" height="16" width="16">'+
69 69 data.owner +'</div>'
70 70 }
71 71 },
72 72 { data: null,
73 73 title: "${_('Action')}",
74 74 className: "td-action",
75 75 defaultContent: '<a href="" class="btn btn-link btn-danger">Delete</a>'
76 76 },
77 77 ],
78 78 language: {
79 79 paginate: DEFAULT_GRID_PAGINATION,
80 80 emptyTable: _gettext("No user groups available yet.")
81 81 },
82 82 "initComplete": function( settings, json ) {
83 83 var data_grid = $('#user_group_list_table').dataTable();
84 84 api = data_grid.api();
85 85 get_datatable_count();
86 86 }
87 87 });
88 88
89 89 // update the counter when doing search
90 90 $('#user_group_list_table').on( 'search.dt', function (e,settings) {
91 91 get_datatable_count();
92 92 });
93 93
94 94 // filter, filter both grids
95 95 $('#q_filter').on( 'keyup', function () {
96 96 var user_api = $('#user_group_list_table').dataTable().api();
97 97 user_api
98 98 .columns(0)
99 99 .search(this.value)
100 100 .draw();
101 101 });
102 102
103 103 // refilter table if page load via back button
104 104 $("#q_filter").trigger('keyup');
105 105
106 106 });
107 107
108 108 $('#language').select2({
109 109 'containerCssClass': "drop-menu",
110 110 'dropdownCssClass': "drop-menu-dropdown",
111 111 'dropdownAutoWidth': true
112 112 });
113 113
114 114
115 115
116 116 $(document).ready(function(){
117 117 $("#group_parent_id").select2({
118 118 'containerCssClass': "drop-menu",
119 119 'dropdownCssClass': "drop-menu-dropdown",
120 120 'dropdownAutoWidth': true
121 121 });
122 122
123 123 $('#add_user_to_group').autocomplete({
124 124 serviceUrl: pyroutes.url('user_group_autocomplete_data'),
125 125 minChars:2,
126 126 maxHeight:400,
127 127 width:300,
128 128 deferRequestBy: 300, //miliseconds
129 129 showNoSuggestionNotice: true,
130 130 params: { user_groups:true },
131 131 formatResult: autocompleteFormatResult,
132 132 lookupFilter: autocompleteFilterResult,
133 133 onSelect: function(element, suggestion){
134 134 var owner = {owner_icon: suggestion.owner_icon, owner:suggestion.owner};
135 135 api.row.add(
136 136 {"active": suggestion.active,
137 137 "owner_data": owner,
138 138 "users_group_id": suggestion.id,
139 139 "group_description": suggestion.description,
140 140 "group_name": suggestion.value}).draw();
141 141 }
142 142 });
143 143 })
144 144
145 145 </script>
146 146
147 147
@@ -1,78 +1,78 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('Custom IP Whitelist')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <div class="ips_wrap">
7 7 <h5>${_('Current IP address')}: <code>${c.rhodecode_user.ip_addr}</code></h5>
8 8 <table class="rctable ip-whitelist">
9 9 <tr>
10 10 <th>${_('IP Address')}</th>
11 11 <th>${_('IP Range')}</th>
12 12 <th>${_('Description')}</th>
13 13 <th></th>
14 14 </tr>
15 15 %if c.default_user_ip_map and c.inherit_default_ips:
16 16 %for ip in c.default_user_ip_map:
17 17 <tr>
18 18 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
19 19 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
20 20 <td class="td-description">${h.literal(_('Inherited from %s') % h.link_to('*default*',h.url('admin_permissions_ips')))}</td>
21 21 <td></td>
22 22 </tr>
23 23 %endfor
24 24 %endif
25 25
26 26 %if c.user_ip_map:
27 27 %for ip in c.user_ip_map:
28 28 <tr>
29 29 <td class="td-ip"><div class="ip">${ip.ip_addr}</div></td>
30 30 <td class="td-iprange"><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
31 31 <td class="td-description"><div class="ip">${ip.description}</div></td>
32 32 <td class="td-action">
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST')}
33 ${h.secure_form(h.route_path('edit_user_ips_delete', user_id=c.user.user_id), method='POST', request=request)}
34 34 ${h.hidden('del_ip_id', ip.ip_id)}
35 35 ${h.submit('remove_', _('Delete'),id="remove_ip_%s" % ip.ip_id,
36 36 class_="btn btn-link btn-danger", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
37 37 ${h.end_form()}
38 38 </td>
39 39 </tr>
40 40 %endfor
41 41 %endif
42 42 %if not c.default_user_ip_map and not c.user_ip_map:
43 43 <tr>
44 44 <td><h2 class="ip">${_('All IP addresses are allowed')}</h2></td>
45 45 <td></td>
46 46 <td></td>
47 47 <td></td>
48 48 </tr>
49 49 %endif
50 50 </table>
51 51 </div>
52 52
53 53 <div>
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST')}
54 ${h.secure_form(h.route_path('edit_user_ips_add', user_id=c.user.user_id), method='POST', request=request)}
55 55 <div class="form">
56 56 <!-- fields -->
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label">
60 60 <label for="new_ip">${_('New IP Address')}:</label>
61 61 </div>
62 62 <div class="input">
63 63 ${h.text('new_ip')} ${h.text('description', placeholder=_('Description...'))}
64 64 <span class="help-block">${_('Enter comma separated list of ip addresses like 127.0.0.1,\n'
65 65 'or use a ip address with a mask 127.0.0.1/24, to create a network range.\n'
66 66 'To specify multiple address range use 127.0.0.1-127.0.0.10 syntax')}</span>
67 67 </div>
68 68 </div>
69 69 <div class="buttons">
70 70 ${h.submit('save',_('Add'),class_="btn btn-small")}
71 71 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76 </div>
77 77 </div>
78 78 </div>
@@ -1,604 +1,604 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.mako"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 </div>
20 20 </div>
21 21 ${self.menu_bar_subnav()}
22 22 <!-- END HEADER -->
23 23
24 24 <!-- CONTENT -->
25 25 <div id="content" class="wrapper">
26 26
27 27 <rhodecode-toast id="notifications"></rhodecode-toast>
28 28
29 29 <div class="main">
30 30 ${next.main()}
31 31 </div>
32 32 </div>
33 33 <!-- END CONTENT -->
34 34
35 35 </div>
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title wrapper">
39 39 <div>
40 40 <p class="footer-link-right">
41 41 % if c.visual.show_version:
42 42 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
43 43 % endif
44 44 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
45 45 % if c.visual.rhodecode_support_url:
46 46 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
47 47 % endif
48 48 </p>
49 49 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
50 50 <p class="server-instance" style="display:${sid}">
51 51 ## display hidden instance ID if specially defined
52 52 % if c.rhodecode_instanceid:
53 53 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
54 54 % endif
55 55 </p>
56 56 </div>
57 57 </div>
58 58 </div>
59 59
60 60 <!-- END FOOTER -->
61 61
62 62 ### MAKO DEFS ###
63 63
64 64 <%def name="menu_bar_subnav()">
65 65 </%def>
66 66
67 67 <%def name="breadcrumbs(class_='breadcrumbs')">
68 68 <div class="${class_}">
69 69 ${self.breadcrumbs_links()}
70 70 </div>
71 71 </%def>
72 72
73 73 <%def name="admin_menu()">
74 74 <ul class="admin_menu submenu">
75 75 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
76 76 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
77 77 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
78 78 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
79 79 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
80 80 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
81 81 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
82 82 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
83 83 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
84 84 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
85 85 </ul>
86 86 </%def>
87 87
88 88
89 89 <%def name="dt_info_panel(elements)">
90 90 <dl class="dl-horizontal">
91 91 %for dt, dd, title, show_items in elements:
92 92 <dt>${dt}:</dt>
93 93 <dd title="${h.tooltip(title)}">
94 94 %if callable(dd):
95 95 ## allow lazy evaluation of elements
96 96 ${dd()}
97 97 %else:
98 98 ${dd}
99 99 %endif
100 100 %if show_items:
101 101 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
102 102 %endif
103 103 </dd>
104 104
105 105 %if show_items:
106 106 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
107 107 %for item in show_items:
108 108 <dt></dt>
109 109 <dd>${item}</dd>
110 110 %endfor
111 111 </div>
112 112 %endif
113 113
114 114 %endfor
115 115 </dl>
116 116 </%def>
117 117
118 118
119 119 <%def name="gravatar(email, size=16)">
120 120 <%
121 121 if (size > 16):
122 122 gravatar_class = 'gravatar gravatar-large'
123 123 else:
124 124 gravatar_class = 'gravatar'
125 125 %>
126 126 <%doc>
127 127 TODO: johbo: For now we serve double size images to make it smooth
128 128 for retina. This is how it worked until now. Should be replaced
129 129 with a better solution at some point.
130 130 </%doc>
131 131 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
132 132 </%def>
133 133
134 134
135 135 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
136 136 <% email = h.email_or_none(contact) %>
137 137 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
138 138 ${self.gravatar(email, size)}
139 139 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
140 140 </div>
141 141 </%def>
142 142
143 143
144 144 ## admin menu used for people that have some admin resources
145 145 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 146 <ul class="submenu">
147 147 %if repositories:
148 148 <li class="local-admin-repos"><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 149 %endif
150 150 %if repository_groups:
151 151 <li class="local-admin-repo-groups"><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 152 %endif
153 153 %if user_groups:
154 154 <li class="local-admin-user-groups"><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 155 %endif
156 156 </ul>
157 157 </%def>
158 158
159 159 <%def name="repo_page_title(repo_instance)">
160 160 <div class="title-content">
161 161 <div class="title-main">
162 162 ## SVN/HG/GIT icons
163 163 %if h.is_hg(repo_instance):
164 164 <i class="icon-hg"></i>
165 165 %endif
166 166 %if h.is_git(repo_instance):
167 167 <i class="icon-git"></i>
168 168 %endif
169 169 %if h.is_svn(repo_instance):
170 170 <i class="icon-svn"></i>
171 171 %endif
172 172
173 173 ## public/private
174 174 %if repo_instance.private:
175 175 <i class="icon-repo-private"></i>
176 176 %else:
177 177 <i class="icon-repo-public"></i>
178 178 %endif
179 179
180 180 ## repo name with group name
181 181 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 182
183 183 </div>
184 184
185 185 ## FORKED
186 186 %if repo_instance.fork:
187 187 <p>
188 188 <i class="icon-code-fork"></i> ${_('Fork of')}
189 189 <a href="${h.route_path('repo_summary',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 190 </p>
191 191 %endif
192 192
193 193 ## IMPORTED FROM REMOTE
194 194 %if repo_instance.clone_uri:
195 195 <p>
196 196 <i class="icon-code-fork"></i> ${_('Clone from')}
197 197 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 198 </p>
199 199 %endif
200 200
201 201 ## LOCKING STATUS
202 202 %if repo_instance.locked[0]:
203 203 <p class="locking_locked">
204 204 <i class="icon-repo-lock"></i>
205 205 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 206 </p>
207 207 %elif repo_instance.enable_locking:
208 208 <p class="locking_unlocked">
209 209 <i class="icon-repo-unlock"></i>
210 210 ${_('Repository not locked. Pull repository to lock it.')}
211 211 </p>
212 212 %endif
213 213
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_menu(active=None)">
218 218 <%
219 219 def is_active(selected):
220 220 if selected == active:
221 221 return "active"
222 222 %>
223 223
224 224 <!--- CONTEXT BAR -->
225 225 <div id="context-bar">
226 226 <div class="wrapper">
227 227 <ul id="context-pages" class="horizontal-list navigation">
228 228 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 229 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 230 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 231 <li class="${is_active('compare')}">
232 232 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 233 </li>
234 234 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 235 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 236 <li class="${is_active('showpullrequest')}">
237 237 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
238 238 %if c.repository_pull_requests:
239 239 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 240 %endif
241 241 <div class="menulabel">${_('Pull Requests')}</div>
242 242 </a>
243 243 </li>
244 244 %endif
245 245 <li class="${is_active('options')}">
246 246 <a class="menulink dropdown">
247 247 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
248 248 </a>
249 249 <ul class="submenu">
250 250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
251 251 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
252 252 %endif
253 253 %if c.rhodecode_db_repo.fork:
254 254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
255 255 ${_('Compare fork')}</a></li>
256 256 %endif
257 257
258 258 <li><a href="${h.route_path('search_repo',repo_name=c.repo_name)}">${_('Search')}</a></li>
259 259
260 260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
261 261 %if c.rhodecode_db_repo.locked[0]:
262 262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
263 263 %else:
264 264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
265 265 %endif
266 266 %endif
267 267 %if c.rhodecode_user.username != h.DEFAULT_USER:
268 268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
269 269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
270 270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
271 271 %endif
272 272 %endif
273 273 </ul>
274 274 </li>
275 275 </ul>
276 276 </div>
277 277 <div class="clear"></div>
278 278 </div>
279 279 <!--- END CONTEXT BAR -->
280 280
281 281 </%def>
282 282
283 283 <%def name="usermenu(active=False)">
284 284 ## USER MENU
285 285 <li id="quick_login_li" class="${'active' if active else ''}">
286 286 <a id="quick_login_link" class="menulink childs">
287 287 ${gravatar(c.rhodecode_user.email, 20)}
288 288 <span class="user">
289 289 %if c.rhodecode_user.username != h.DEFAULT_USER:
290 290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
291 291 %else:
292 292 <span>${_('Sign in')}</span>
293 293 %endif
294 294 </span>
295 295 </a>
296 296
297 297 <div class="user-menu submenu">
298 298 <div id="quick_login">
299 299 %if c.rhodecode_user.username == h.DEFAULT_USER:
300 300 <h4>${_('Sign in to your account')}</h4>
301 301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
302 302 <div class="form form-vertical">
303 303 <div class="fields">
304 304 <div class="field">
305 305 <div class="label">
306 306 <label for="username">${_('Username')}:</label>
307 307 </div>
308 308 <div class="input">
309 309 ${h.text('username',class_='focus',tabindex=1)}
310 310 </div>
311 311
312 312 </div>
313 313 <div class="field">
314 314 <div class="label">
315 315 <label for="password">${_('Password')}:</label>
316 316 %if h.HasPermissionAny('hg.password_reset.enabled')():
317 317 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'), class_='pwd_reset')}</span>
318 318 %endif
319 319 </div>
320 320 <div class="input">
321 321 ${h.password('password',class_='focus',tabindex=2)}
322 322 </div>
323 323 </div>
324 324 <div class="buttons">
325 325 <div class="register">
326 326 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
327 327 ${h.link_to(_("Don't have an account?"),h.route_path('register'))} <br/>
328 328 %endif
329 329 ${h.link_to(_("Using external auth? Sign In here."),h.route_path('login'))}
330 330 </div>
331 331 <div class="submit">
332 332 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
333 333 </div>
334 334 </div>
335 335 </div>
336 336 </div>
337 337 ${h.end_form()}
338 338 %else:
339 339 <div class="">
340 340 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
341 341 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
342 342 <div class="email">${c.rhodecode_user.email}</div>
343 343 </div>
344 344 <div class="">
345 345 <ol class="links">
346 346 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
347 347 % if c.rhodecode_user.personal_repo_group:
348 348 <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>
349 349 % endif
350 350 <li class="logout">
351 ${h.secure_form(h.route_path('logout'))}
351 ${h.secure_form(h.route_path('logout'), request=request)}
352 352 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
353 353 ${h.end_form()}
354 354 </li>
355 355 </ol>
356 356 </div>
357 357 %endif
358 358 </div>
359 359 </div>
360 360 %if c.rhodecode_user.username != h.DEFAULT_USER:
361 361 <div class="pill_container">
362 362 % if c.unread_notifications == 0:
363 363 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
364 364 % else:
365 365 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
366 366 % endif
367 367 </div>
368 368 % endif
369 369 </li>
370 370 </%def>
371 371
372 372 <%def name="menu_items(active=None)">
373 373 <%
374 374 def is_active(selected):
375 375 if selected == active:
376 376 return "active"
377 377 return ""
378 378 %>
379 379 <ul id="quick" class="main_nav navigation horizontal-list">
380 380 <!-- repo switcher -->
381 381 <li class="${is_active('repositories')} repo_switcher_li has_select2">
382 382 <input id="repo_switcher" name="repo_switcher" type="hidden">
383 383 </li>
384 384
385 385 ## ROOT MENU
386 386 %if c.rhodecode_user.username != h.DEFAULT_USER:
387 387 <li class="${is_active('journal')}">
388 388 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
389 389 <div class="menulabel">${_('Journal')}</div>
390 390 </a>
391 391 </li>
392 392 %else:
393 393 <li class="${is_active('journal')}">
394 394 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
395 395 <div class="menulabel">${_('Public journal')}</div>
396 396 </a>
397 397 </li>
398 398 %endif
399 399 <li class="${is_active('gists')}">
400 400 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
401 401 <div class="menulabel">${_('Gists')}</div>
402 402 </a>
403 403 </li>
404 404 <li class="${is_active('search')}">
405 405 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
406 406 <div class="menulabel">${_('Search')}</div>
407 407 </a>
408 408 </li>
409 409 % if h.HasPermissionAll('hg.admin')('access admin main page'):
410 410 <li class="${is_active('admin')}">
411 411 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
412 412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 413 </a>
414 414 ${admin_menu()}
415 415 </li>
416 416 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
417 417 <li class="${is_active('admin')}">
418 418 <a class="menulink childs" title="${_('Delegated Admin settings')}">
419 419 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
420 420 </a>
421 421 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
422 422 c.rhodecode_user.repository_groups_admin,
423 423 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
424 424 </li>
425 425 % endif
426 426 % if c.debug_style:
427 427 <li class="${is_active('debug_style')}">
428 428 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
429 429 <div class="menulabel">${_('Style')}</div>
430 430 </a>
431 431 </li>
432 432 % endif
433 433 ## render extra user menu
434 434 ${usermenu(active=(active=='my_account'))}
435 435 </ul>
436 436
437 437 <script type="text/javascript">
438 438 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
439 439
440 440 /*format the look of items in the list*/
441 441 var format = function(state, escapeMarkup){
442 442 if (!state.id){
443 443 return state.text; // optgroup
444 444 }
445 445 var obj_dict = state.obj;
446 446 var tmpl = '';
447 447
448 448 if(obj_dict && state.type == 'repo'){
449 449 if(obj_dict['repo_type'] === 'hg'){
450 450 tmpl += '<i class="icon-hg"></i> ';
451 451 }
452 452 else if(obj_dict['repo_type'] === 'git'){
453 453 tmpl += '<i class="icon-git"></i> ';
454 454 }
455 455 else if(obj_dict['repo_type'] === 'svn'){
456 456 tmpl += '<i class="icon-svn"></i> ';
457 457 }
458 458 if(obj_dict['private']){
459 459 tmpl += '<i class="icon-lock" ></i> ';
460 460 }
461 461 else if(visual_show_public_icon){
462 462 tmpl += '<i class="icon-unlock-alt"></i> ';
463 463 }
464 464 }
465 465 if(obj_dict && state.type == 'commit') {
466 466 tmpl += '<i class="icon-tag"></i>';
467 467 }
468 468 if(obj_dict && state.type == 'group'){
469 469 tmpl += '<i class="icon-folder-close"></i> ';
470 470 }
471 471 tmpl += escapeMarkup(state.text);
472 472 return tmpl;
473 473 };
474 474
475 475 var formatResult = function(result, container, query, escapeMarkup) {
476 476 return format(result, escapeMarkup);
477 477 };
478 478
479 479 var formatSelection = function(data, container, escapeMarkup) {
480 480 return format(data, escapeMarkup);
481 481 };
482 482
483 483 $("#repo_switcher").select2({
484 484 cachedDataSource: {},
485 485 minimumInputLength: 2,
486 486 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
487 487 dropdownAutoWidth: true,
488 488 formatResult: formatResult,
489 489 formatSelection: formatSelection,
490 490 containerCssClass: "repo-switcher",
491 491 dropdownCssClass: "repo-switcher-dropdown",
492 492 escapeMarkup: function(m){
493 493 // don't escape our custom placeholder
494 494 if(m.substr(0,23) == '<div class="menulabel">'){
495 495 return m;
496 496 }
497 497
498 498 return Select2.util.escapeMarkup(m);
499 499 },
500 500 query: $.debounce(250, function(query){
501 501 self = this;
502 502 var cacheKey = query.term;
503 503 var cachedData = self.cachedDataSource[cacheKey];
504 504
505 505 if (cachedData) {
506 506 query.callback({results: cachedData.results});
507 507 } else {
508 508 $.ajax({
509 509 url: pyroutes.url('goto_switcher_data'),
510 510 data: {'query': query.term},
511 511 dataType: 'json',
512 512 type: 'GET',
513 513 success: function(data) {
514 514 self.cachedDataSource[cacheKey] = data;
515 515 query.callback({results: data.results});
516 516 },
517 517 error: function(data, textStatus, errorThrown) {
518 518 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
519 519 }
520 520 })
521 521 }
522 522 })
523 523 });
524 524
525 525 $("#repo_switcher").on('select2-selecting', function(e){
526 526 e.preventDefault();
527 527 window.location = e.choice.url;
528 528 });
529 529
530 530 </script>
531 531 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
532 532 </%def>
533 533
534 534 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
535 535 <div class="modal-dialog">
536 536 <div class="modal-content">
537 537 <div class="modal-header">
538 538 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
539 539 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
540 540 </div>
541 541 <div class="modal-body">
542 542 <div class="block-left">
543 543 <table class="keyboard-mappings">
544 544 <tbody>
545 545 <tr>
546 546 <th></th>
547 547 <th>${_('Site-wide shortcuts')}</th>
548 548 </tr>
549 549 <%
550 550 elems = [
551 551 ('/', 'Open quick search box'),
552 552 ('g h', 'Goto home page'),
553 553 ('g g', 'Goto my private gists page'),
554 554 ('g G', 'Goto my public gists page'),
555 555 ('n r', 'New repository page'),
556 556 ('n g', 'New gist page'),
557 557 ]
558 558 %>
559 559 %for key, desc in elems:
560 560 <tr>
561 561 <td class="keys">
562 562 <span class="key tag">${key}</span>
563 563 </td>
564 564 <td>${desc}</td>
565 565 </tr>
566 566 %endfor
567 567 </tbody>
568 568 </table>
569 569 </div>
570 570 <div class="block-left">
571 571 <table class="keyboard-mappings">
572 572 <tbody>
573 573 <tr>
574 574 <th></th>
575 575 <th>${_('Repositories')}</th>
576 576 </tr>
577 577 <%
578 578 elems = [
579 579 ('g s', 'Goto summary page'),
580 580 ('g c', 'Goto changelog page'),
581 581 ('g f', 'Goto files page'),
582 582 ('g F', 'Goto files page with file search activated'),
583 583 ('g p', 'Goto pull requests page'),
584 584 ('g o', 'Goto repository settings'),
585 585 ('g O', 'Goto repository permissions settings'),
586 586 ]
587 587 %>
588 588 %for key, desc in elems:
589 589 <tr>
590 590 <td class="keys">
591 591 <span class="key tag">${key}</span>
592 592 </td>
593 593 <td>${desc}</td>
594 594 </tr>
595 595 %endfor
596 596 </tbody>
597 597 </table>
598 598 </div>
599 599 </div>
600 600 <div class="modal-footer">
601 601 </div>
602 602 </div><!-- /.modal-content -->
603 603 </div><!-- /.modal-dialog -->
604 604 </div><!-- /.modal -->
@@ -1,317 +1,317 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 ## REPOSITORY RENDERERS
7 7 <%def name="quick_menu(repo_name)">
8 8 <i class="pointer icon-more"></i>
9 9 <div class="menu_items_container hidden">
10 10 <ul class="menu_items">
11 11 <li>
12 12 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
13 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
16 16 <li>
17 17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 18 <span>${_('Changelog')}</span>
19 19 </a>
20 20 </li>
21 21 <li>
22 22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 23 <span>${_('Files')}</span>
24 24 </a>
25 25 </li>
26 26 <li>
27 27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 28 <span>${_('Fork')}</span>
29 29 </a>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 </%def>
34 34
35 35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 36 <%
37 37 def get_name(name,short_name=short_name):
38 38 if short_name:
39 39 return name.split('/')[-1]
40 40 else:
41 41 return name
42 42 %>
43 43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 44 ##NAME
45 45 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
46 46
47 47 ##TYPE OF REPO
48 48 %if h.is_hg(rtype):
49 49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 50 %elif h.is_git(rtype):
51 51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 52 %elif h.is_svn(rtype):
53 53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 54 %endif
55 55
56 56 ##PRIVATE/PUBLIC
57 57 %if private and c.visual.show_private_icon:
58 58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 59 %elif not private and c.visual.show_public_icon:
60 60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 61 %else:
62 62 <span></span>
63 63 %endif
64 64 ${get_name(name)}
65 65 </a>
66 66 %if fork_of:
67 67 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 68 %endif
69 69 %if rstate == 'repo_state_pending':
70 70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 71 %endif
72 72 </div>
73 73 </%def>
74 74
75 75 <%def name="repo_desc(description)">
76 76 <div class="truncate-wrap">${description}</div>
77 77 </%def>
78 78
79 79 <%def name="last_change(last_change)">
80 80 ${h.age_component(last_change)}
81 81 </%def>
82 82
83 83 <%def name="revision(name,rev,tip,author,last_msg)">
84 84 <div>
85 85 %if rev >= 0:
86 86 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
87 87 %else:
88 88 ${_('No commits yet')}
89 89 %endif
90 90 </div>
91 91 </%def>
92 92
93 93 <%def name="rss(name)">
94 94 %if c.rhodecode_user.username != h.DEFAULT_USER:
95 95 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
96 96 %else:
97 97 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
98 98 %endif
99 99 </%def>
100 100
101 101 <%def name="atom(name)">
102 102 %if c.rhodecode_user.username != h.DEFAULT_USER:
103 103 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
104 104 %else:
105 105 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
106 106 %endif
107 107 </%def>
108 108
109 109 <%def name="user_gravatar(email, size=16)">
110 110 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
111 111 ${base.gravatar(email, 16)}
112 112 </div>
113 113 </%def>
114 114
115 115 <%def name="repo_actions(repo_name, super_user=True)">
116 116 <div>
117 117 <div class="grid_edit">
118 118 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
119 119 <i class="icon-pencil"></i>Edit</a>
120 120 </div>
121 121 <div class="grid_delete">
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST')}
122 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), method='POST', request=request)}
123 123 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
124 124 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
125 125 ${h.end_form()}
126 126 </div>
127 127 </div>
128 128 </%def>
129 129
130 130 <%def name="repo_state(repo_state)">
131 131 <div>
132 132 %if repo_state == 'repo_state_pending':
133 133 <div class="tag tag4">${_('Creating')}</div>
134 134 %elif repo_state == 'repo_state_created':
135 135 <div class="tag tag1">${_('Created')}</div>
136 136 %else:
137 137 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
138 138 %endif
139 139 </div>
140 140 </%def>
141 141
142 142
143 143 ## REPO GROUP RENDERERS
144 144 <%def name="quick_repo_group_menu(repo_group_name)">
145 145 <i class="pointer icon-more"></i>
146 146 <div class="menu_items_container hidden">
147 147 <ul class="menu_items">
148 148 <li>
149 149 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
150 150 <span class="icon">
151 151 <i class="icon-file-text"></i>
152 152 </span>
153 153 <span>${_('Summary')}</span>
154 154 </a>
155 155 </li>
156 156
157 157 </ul>
158 158 </div>
159 159 </%def>
160 160
161 161 <%def name="repo_group_name(repo_group_name, children_groups=None)">
162 162 <div>
163 163 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
164 164 <i class="icon-folder-close" title="${_('Repository group')}"></i>
165 165 %if children_groups:
166 166 ${h.literal(' &raquo; '.join(children_groups))}
167 167 %else:
168 168 ${repo_group_name}
169 169 %endif
170 170 </a>
171 171 </div>
172 172 </%def>
173 173
174 174 <%def name="repo_group_desc(description)">
175 175 <div class="truncate-wrap">${description}</div>
176 176 </%def>
177 177
178 178 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
179 179 <div class="grid_edit">
180 180 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
181 181 </div>
182 182 <div class="grid_delete">
183 183 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
184 184 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
185 185 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
186 186 ${h.end_form()}
187 187 </div>
188 188 </%def>
189 189
190 190
191 191 <%def name="user_actions(user_id, username)">
192 192 <div class="grid_edit">
193 193 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
194 194 <i class="icon-pencil"></i>Edit</a>
195 195 </div>
196 196 <div class="grid_delete">
197 197 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
198 198 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
199 199 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
200 200 ${h.end_form()}
201 201 </div>
202 202 </%def>
203 203
204 204 <%def name="user_group_actions(user_group_id, user_group_name)">
205 205 <div class="grid_edit">
206 206 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
207 207 </div>
208 208 <div class="grid_delete">
209 209 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
210 210 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
211 211 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
212 212 ${h.end_form()}
213 213 </div>
214 214 </%def>
215 215
216 216
217 217 <%def name="user_name(user_id, username)">
218 218 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
219 219 </%def>
220 220
221 221 <%def name="user_profile(username)">
222 222 ${base.gravatar_with_user(username, 16)}
223 223 </%def>
224 224
225 225 <%def name="user_group_name(user_group_id, user_group_name)">
226 226 <div>
227 227 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
228 228 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
229 229 </div>
230 230 </%def>
231 231
232 232
233 233 ## GISTS
234 234
235 235 <%def name="gist_gravatar(full_contact)">
236 236 <div class="gist_gravatar">
237 237 ${base.gravatar(full_contact, 30)}
238 238 </div>
239 239 </%def>
240 240
241 241 <%def name="gist_access_id(gist_access_id, full_contact)">
242 242 <div>
243 243 <b>
244 244 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
245 245 </b>
246 246 </div>
247 247 </%def>
248 248
249 249 <%def name="gist_author(full_contact, created_on, expires)">
250 250 ${base.gravatar_with_user(full_contact, 16)}
251 251 </%def>
252 252
253 253
254 254 <%def name="gist_created(created_on)">
255 255 <div class="created">
256 256 ${h.age_component(created_on, time_is_local=True)}
257 257 </div>
258 258 </%def>
259 259
260 260 <%def name="gist_expires(expires)">
261 261 <div class="created">
262 262 %if expires == -1:
263 263 ${_('never')}
264 264 %else:
265 265 ${h.age_component(h.time_to_utcdatetime(expires))}
266 266 %endif
267 267 </div>
268 268 </%def>
269 269
270 270 <%def name="gist_type(gist_type)">
271 271 %if gist_type != 'public':
272 272 <div class="tag">${_('Private')}</div>
273 273 %endif
274 274 </%def>
275 275
276 276 <%def name="gist_description(gist_description)">
277 277 ${gist_description}
278 278 </%def>
279 279
280 280
281 281 ## PULL REQUESTS GRID RENDERERS
282 282
283 283 <%def name="pullrequest_target_repo(repo_name)">
284 284 <div class="truncate">
285 285 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
286 286 </div>
287 287 </%def>
288 288 <%def name="pullrequest_status(status)">
289 289 <div class="${'flag_status %s' % status} pull-left"></div>
290 290 </%def>
291 291
292 292 <%def name="pullrequest_title(title, description)">
293 293 ${title} <br/>
294 294 ${h.shorter(description, 40)}
295 295 </%def>
296 296
297 297 <%def name="pullrequest_comments(comments_nr)">
298 298 <i class="icon-comment"></i> ${comments_nr}
299 299 </%def>
300 300
301 301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 303 % if short:
304 304 #${pull_request_id}
305 305 % else:
306 306 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
307 307 % endif
308 308 </a>
309 309 </%def>
310 310
311 311 <%def name="pullrequest_updated_on(updated_on)">
312 312 ${h.age_component(h.time_to_utcdatetime(updated_on))}
313 313 </%def>
314 314
315 315 <%def name="pullrequest_author(full_contact)">
316 316 ${base.gravatar_with_user(full_contact, 16)}
317 317 </%def>
@@ -1,100 +1,100 b''
1 1 <form
2 2 tal:define="style style|field.widget.style;
3 3 css_class css_class|string:${field.widget.css_class or field.css_class or ''};
4 4 item_template item_template|field.widget.item_template;
5 5 autocomplete autocomplete|field.autocomplete;
6 6 title title|field.title;
7 7 errormsg errormsg|field.errormsg;
8 8 description description|field.description;
9 9 buttons buttons|field.buttons;
10 10 use_ajax use_ajax|field.use_ajax;
11 11 ajax_options ajax_options|field.ajax_options;
12 12 formid formid|field.formid;
13 13 action action|field.action or None;
14 14 method method|field.method;"
15 15 tal:attributes="autocomplete autocomplete;
16 16 style style;
17 17 class css_class;
18 18 action action;"
19 19 id="${formid}"
20 20 method="${method}"
21 21 enctype="multipart/form-data"
22 22 accept-charset="utf-8"
23 23 i18n:domain="deform"
24 24 >
25 25
26 26 <fieldset class="deform-form-fieldset">
27 27
28 28 <legend tal:condition="title">${title}</legend>
29 29
30 <input type="hidden" name="${h.csrf_token_key}" value="${h.get_csrf_token()}" />
30 <input type="hidden" name="${h.csrf_token_key}" value="${h.get_csrf_token(request.session)}" />
31 31 <input type="hidden" name="_charset_" />
32 32 <input type="hidden" name="__formid__" value="${formid}"/>
33 33
34 34 <!--
35 35 <div class="alert alert-danger" tal:condition="field.error">
36 36 <div class="error-msg-lbl" i18n:translate=""
37 37 >There was a problem with your submission</div>
38 38 <div class="error-msg-detail" i18n:translate=""
39 39 >Errors have been highlighted below</div>
40 40 <p class="error-msg">${field.errormsg}</p>
41 41 </div>
42 42 -->
43 43
44 44 <p class="section first" tal:condition="description">
45 45 ${description}
46 46 </p>
47 47
48 48 <div tal:repeat="child field"
49 49 tal:replace="structure child.render_template(item_template)"/>
50 50
51 51 <div class="form-group">
52 52 <tal:loop tal:repeat="button buttons">
53 53 <button
54 54 tal:define="btn_disposition repeat.button.start and 'btn-primary' or (button.name == 'delete' and 'btn-danger' or 'btn-default');
55 55 btn_icon button.icon|None"
56 56 tal:attributes="disabled button.disabled if button.disabled else None"
57 57 id="${formid+button.name}"
58 58 name="${button.name}"
59 59 type="${button.type}"
60 60 class="btn ${button.css_class or btn_disposition}"
61 61 value="${button.value}">
62 62 <i tal:condition="btn_icon" class="${btn_icon}"> </i>
63 63 ${button.title}
64 64 </button>
65 65 </tal:loop>
66 66 </div>
67 67
68 68 </fieldset>
69 69
70 70 <script type="text/javascript" tal:condition="use_ajax">
71 71 deform.addCallback(
72 72 '${formid}',
73 73 function(oid) {
74 74 var target = '#' + oid;
75 75 var options = {
76 76 target: target,
77 77 replaceTarget: true,
78 78 success: function() {
79 79 deform.processCallbacks();
80 80 deform.focusFirstInput(target);
81 81 },
82 82 beforeSerialize: function() {
83 83 // See http://bit.ly/1agBs9Z (hack to fix tinymce-related ajax bug)
84 84 if ('tinymce' in window) {
85 85 $(tinymce.get()).each(
86 86 function(i, el) {
87 87 var content = el.getContent();
88 88 var editor_input = document.getElementById(el.id);
89 89 editor_input.value = content;
90 90 });
91 91 }
92 92 }
93 93 };
94 94 var extra_options = ${ajax_options} || {};
95 95 $('#' + oid).ajaxForm($.extend(options, extra_options));
96 96 }
97 97 );
98 98 </script>
99 99
100 100 </form> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now