##// END OF EJS Templates
white space cleanup
marcink -
r1944:5fc9c920 beta
parent child Browse files
Show More
@@ -1,414 +1,414 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from vcs import get_backend
41 41
42 42 from rhodecode import CELERY_ON
43 43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
44 44 from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
45 45 str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
46 46 from rhodecode.lib.helpers import person
47 47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 48 from rhodecode.lib.utils import add_cache, action_logger
49 49 from rhodecode.lib.compat import json, OrderedDict
50 50
51 51 from rhodecode.model.db import Statistics, Repository, User
52 52
53 53
54 54 add_cache(config)
55 55
56 56 __all__ = ['whoosh_index', 'get_commits_stats',
57 57 'reset_user_password', 'send_email']
58 58
59 59
60 60 def get_logger(cls):
61 61 if CELERY_ON:
62 62 try:
63 63 log = cls.get_logger()
64 64 except:
65 65 log = logging.getLogger(__name__)
66 66 else:
67 67 log = logging.getLogger(__name__)
68 68
69 69 return log
70 70
71 71
72 72 @task(ignore_result=True)
73 73 @locked_task
74 74 @dbsession
75 75 def whoosh_index(repo_location, full_index):
76 76 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
77 77 log = whoosh_index.get_logger(whoosh_index)
78 78 DBS = get_session()
79 79
80 80 index_location = config['index_dir']
81 81 WhooshIndexingDaemon(index_location=index_location,
82 82 repo_location=repo_location, sa=DBS)\
83 83 .run(full_index=full_index)
84 84
85 85
86 86 @task(ignore_result=True)
87 87 @dbsession
88 88 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
89 89 log = get_logger(get_commits_stats)
90 90 DBS = get_session()
91 91 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
92 92 ts_max_y)
93 93 lockkey_path = config['here']
94 94
95 95 log.info('running task with lockkey %s', lockkey)
96 96
97 97 try:
98 98 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
99 99
100 100 # for js data compatibilty cleans the key for person from '
101 101 akc = lambda k: person(k).replace('"', "")
102 102
103 103 co_day_auth_aggr = {}
104 104 commits_by_day_aggregate = {}
105 105 repo = Repository.get_by_repo_name(repo_name)
106 106 if repo is None:
107 107 return True
108 108
109 109 repo = repo.scm_instance
110 110 repo_size = repo.count()
111 111 # return if repo have no revisions
112 112 if repo_size < 1:
113 113 lock.release()
114 114 return True
115 115
116 116 skip_date_limit = True
117 117 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
118 118 last_rev = None
119 119 last_cs = None
120 120 timegetter = itemgetter('time')
121 121
122 122 dbrepo = DBS.query(Repository)\
123 123 .filter(Repository.repo_name == repo_name).scalar()
124 124 cur_stats = DBS.query(Statistics)\
125 125 .filter(Statistics.repository == dbrepo).scalar()
126 126
127 127 if cur_stats is not None:
128 128 last_rev = cur_stats.stat_on_revision
129 129
130 130 if last_rev == repo.get_changeset().revision and repo_size > 1:
131 131 # pass silently without any work if we're not on first revision or
132 132 # current state of parsing revision(from db marker) is the
133 133 # last revision
134 134 lock.release()
135 135 return True
136 136
137 137 if cur_stats:
138 138 commits_by_day_aggregate = OrderedDict(json.loads(
139 139 cur_stats.commit_activity_combined))
140 140 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
141 141
142 142 log.debug('starting parsing %s', parse_limit)
143 143 lmktime = mktime
144 144
145 145 last_rev = last_rev + 1 if last_rev >= 0 else 0
146 146 log.debug('Getting revisions from %s to %s' % (
147 147 last_rev, last_rev + parse_limit)
148 148 )
149 149 for cs in repo[last_rev:last_rev + parse_limit]:
150 150 last_cs = cs # remember last parsed changeset
151 151 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 152 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
153 153
154 154 if akc(cs.author) in co_day_auth_aggr:
155 155 try:
156 156 l = [timegetter(x) for x in
157 157 co_day_auth_aggr[akc(cs.author)]['data']]
158 158 time_pos = l.index(k)
159 159 except ValueError:
160 160 time_pos = False
161 161
162 162 if time_pos >= 0 and time_pos is not False:
163 163
164 164 datadict = \
165 165 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
166 166
167 167 datadict["commits"] += 1
168 168 datadict["added"] += len(cs.added)
169 169 datadict["changed"] += len(cs.changed)
170 170 datadict["removed"] += len(cs.removed)
171 171
172 172 else:
173 173 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
174 174
175 175 datadict = {"time": k,
176 176 "commits": 1,
177 177 "added": len(cs.added),
178 178 "changed": len(cs.changed),
179 179 "removed": len(cs.removed),
180 180 }
181 181 co_day_auth_aggr[akc(cs.author)]['data']\
182 182 .append(datadict)
183 183
184 184 else:
185 185 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
186 186 co_day_auth_aggr[akc(cs.author)] = {
187 187 "label": akc(cs.author),
188 188 "data": [{"time":k,
189 189 "commits":1,
190 190 "added":len(cs.added),
191 191 "changed":len(cs.changed),
192 192 "removed":len(cs.removed),
193 193 }],
194 194 "schema": ["commits"],
195 195 }
196 196
197 197 #gather all data by day
198 198 if k in commits_by_day_aggregate:
199 199 commits_by_day_aggregate[k] += 1
200 200 else:
201 201 commits_by_day_aggregate[k] = 1
202 202
203 203 overview_data = sorted(commits_by_day_aggregate.items(),
204 204 key=itemgetter(0))
205 205
206 206 if not co_day_auth_aggr:
207 207 co_day_auth_aggr[akc(repo.contact)] = {
208 208 "label": akc(repo.contact),
209 209 "data": [0, 1],
210 210 "schema": ["commits"],
211 211 }
212 212
213 213 stats = cur_stats if cur_stats else Statistics()
214 214 stats.commit_activity = json.dumps(co_day_auth_aggr)
215 215 stats.commit_activity_combined = json.dumps(overview_data)
216 216
217 217 log.debug('last revison %s', last_rev)
218 218 leftovers = len(repo.revisions[last_rev:])
219 219 log.debug('revisions to parse %s', leftovers)
220 220
221 221 if last_rev == 0 or leftovers < parse_limit:
222 222 log.debug('getting code trending stats')
223 223 stats.languages = json.dumps(__get_codes_stats(repo_name))
224 224
225 225 try:
226 226 stats.repository = dbrepo
227 227 stats.stat_on_revision = last_cs.revision if last_cs else 0
228 228 DBS.add(stats)
229 229 DBS.commit()
230 230 except:
231 231 log.error(traceback.format_exc())
232 232 DBS.rollback()
233 233 lock.release()
234 234 return False
235 235
236 236 #final release
237 237 lock.release()
238 238
239 239 #execute another task if celery is enabled
240 240 if len(repo.revisions) > 1 and CELERY_ON:
241 241 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
242 242 return True
243 243 except LockHeld:
244 244 log.info('LockHeld')
245 245 return 'Task with key %s already running' % lockkey
246 246
247 247 @task(ignore_result=True)
248 248 @dbsession
249 249 def send_password_link(user_email):
250 250 from rhodecode.model.notification import EmailNotificationModel
251 251
252 252 log = get_logger(send_password_link)
253 253 DBS = get_session()
254
254
255 255 try:
256 256 user = User.get_by_email(user_email)
257 257 if user:
258 258 log.debug('password reset user found %s' % user)
259 259 link = url('reset_password_confirmation', key=user.api_key,
260 260 qualified=True)
261 261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
262 262 body = EmailNotificationModel().get_email_tmpl(reg_type,
263 263 **{'user':user.short_contact,
264 264 'reset_url':link})
265 265 log.debug('sending email')
266 266 run_task(send_email, user_email,
267 267 _("password reset link"), body)
268 268 log.info('send new password mail to %s', user_email)
269 269 else:
270 270 log.debug("password reset email %s not found" % user_email)
271 271 except:
272 272 log.error(traceback.format_exc())
273 273 return False
274 274
275 275 return True
276 276
277 277 @task(ignore_result=True)
278 278 @dbsession
279 279 def reset_user_password(user_email):
280 280 from rhodecode.lib import auth
281 281
282 282 log = get_logger(reset_user_password)
283 283 DBS = get_session()
284
284
285 285 try:
286 286 try:
287 287 user = User.get_by_email(user_email)
288 288 new_passwd = auth.PasswordGenerator().gen_password(8,
289 289 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
290 290 if user:
291 291 user.password = auth.get_crypt_password(new_passwd)
292 292 user.api_key = auth.generate_api_key(user.username)
293 293 DBS.add(user)
294 294 DBS.commit()
295 295 log.info('change password for %s', user_email)
296 296 if new_passwd is None:
297 297 raise Exception('unable to generate new password')
298 298 except:
299 299 log.error(traceback.format_exc())
300 300 DBS.rollback()
301 301
302 302 run_task(send_email, user_email,
303 303 'Your new password',
304 304 'Your new RhodeCode password:%s' % (new_passwd))
305 305 log.info('send new password mail to %s', user_email)
306 306
307 307 except:
308 308 log.error('Failed to update user password')
309 309 log.error(traceback.format_exc())
310 310
311 311 return True
312 312
313 313
314 314 @task(ignore_result=True)
315 315 @dbsession
316 316 def send_email(recipients, subject, body, html_body=''):
317 317 """
318 318 Sends an email with defined parameters from the .ini files.
319 319
320 320 :param recipients: list of recipients, it this is empty the defined email
321 321 address from field 'email_to' is used instead
322 322 :param subject: subject of the mail
323 323 :param body: body of the mail
324 324 :param html_body: html version of body
325 325 """
326 326 log = get_logger(send_email)
327 327 DBS = get_session()
328
328
329 329 email_config = config
330 330 subject = "%s %s" % (email_config.get('email_prefix'), subject)
331 331 if not recipients:
332 332 # if recipients are not defined we send to email_config + all admins
333 333 admins = [u.email for u in User.query()
334 334 .filter(User.admin == True).all()]
335 335 recipients = [email_config.get('email_to')] + admins
336 336
337 337 mail_from = email_config.get('app_email_from', 'RhodeCode')
338 338 user = email_config.get('smtp_username')
339 339 passwd = email_config.get('smtp_password')
340 340 mail_server = email_config.get('smtp_server')
341 341 mail_port = email_config.get('smtp_port')
342 342 tls = str2bool(email_config.get('smtp_use_tls'))
343 343 ssl = str2bool(email_config.get('smtp_use_ssl'))
344 344 debug = str2bool(config.get('debug'))
345 345 smtp_auth = email_config.get('smtp_auth')
346 346
347 347 try:
348 348 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
349 349 mail_port, ssl, tls, debug=debug)
350 350 m.send(recipients, subject, body, html_body)
351 351 except:
352 352 log.error('Mail sending failed')
353 353 log.error(traceback.format_exc())
354 354 return False
355 355 return True
356 356
357 357
358 358 @task(ignore_result=True)
359 359 @dbsession
360 360 def create_repo_fork(form_data, cur_user):
361 361 """
362 362 Creates a fork of repository using interval VCS methods
363 363
364 364 :param form_data:
365 365 :param cur_user:
366 366 """
367 367 from rhodecode.model.repo import RepoModel
368 368
369 369 log = get_logger(create_repo_fork)
370 370 DBS = get_session()
371 371
372 372 base_path = Repository.base_path()
373 373
374 374 RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
375 375
376 376 alias = form_data['repo_type']
377 377 org_repo_name = form_data['org_path']
378 378 fork_name = form_data['repo_name_full']
379 379 update_after_clone = form_data['update_after_clone']
380 380 source_repo_path = os.path.join(base_path, org_repo_name)
381 381 destination_fork_path = os.path.join(base_path, fork_name)
382 382
383 383 log.info('creating fork of %s as %s', source_repo_path,
384 384 destination_fork_path)
385 385 backend = get_backend(alias)
386 386 backend(safe_str(destination_fork_path), create=True,
387 387 src_url=safe_str(source_repo_path),
388 388 update_after_clone=update_after_clone)
389 389 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
390 390 org_repo_name, '', DBS)
391 391
392 392 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
393 393 fork_name, '', DBS)
394 394 # finally commit at latest possible stage
395 395 DBS.commit()
396 396
397 397 def __get_codes_stats(repo_name):
398 398 repo = Repository.get_by_repo_name(repo_name).scm_instance
399 399
400 400 tip = repo.get_changeset()
401 401 code_stats = {}
402 402
403 403 def aggregate(cs):
404 404 for f in cs[2]:
405 405 ext = lower(f.extension)
406 406 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
407 407 if ext in code_stats:
408 408 code_stats[ext] += 1
409 409 else:
410 410 code_stats[ext] = 1
411 411
412 412 map(aggregate, tip.walk('/'))
413 413
414 414 return code_stats or {}
@@ -1,846 +1,846 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12
13 13 from datetime import datetime
14 14 from pygments.formatters.html import HtmlFormatter
15 15 from pygments import highlight as code_highlight
16 16 from pylons import url, request, config
17 17 from pylons.i18n.translation import _, ungettext
18 18 from hashlib import md5
19 19
20 20 from webhelpers.html import literal, HTML, escape
21 21 from webhelpers.html.tools import *
22 22 from webhelpers.html.builder import make_tag
23 23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 29 from webhelpers.number import format_byte_size, format_bit_size
30 30 from webhelpers.pylonslib import Flash as _Flash
31 31 from webhelpers.pylonslib.secure_form import secure_form
32 32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 35 from webhelpers.date import time_ago_in_words
36 36 from webhelpers.paginate import Page
37 37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39 39
40 40 from rhodecode.lib.annotate import annotate_highlight
41 41 from rhodecode.lib.utils import repo_name_slug
42 42 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
43 43 from rhodecode.lib.markup_renderer import MarkupRenderer
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
49 49 """
50 50 Reset button
51 51 """
52 52 _set_input_attrs(attrs, type, name, value)
53 53 _set_id_attr(attrs, id, name)
54 54 convert_boolean_attrs(attrs, ["disabled"])
55 55 return HTML.input(**attrs)
56 56
57 57 reset = _reset
58 58 safeid = _make_safe_id_component
59 59
60 60
61 61 def FID(raw_id, path):
62 62 """
63 63 Creates a uniqe ID for filenode based on it's hash of path and revision
64 64 it's safe to use in urls
65 65
66 66 :param raw_id:
67 67 :param path:
68 68 """
69 69
70 70 return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
71 71
72 72
73 73 def get_token():
74 74 """Return the current authentication token, creating one if one doesn't
75 75 already exist.
76 76 """
77 77 token_key = "_authentication_token"
78 78 from pylons import session
79 79 if not token_key in session:
80 80 try:
81 81 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
82 82 except AttributeError: # Python < 2.4
83 83 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
84 84 session[token_key] = token
85 85 if hasattr(session, 'save'):
86 86 session.save()
87 87 return session[token_key]
88 88
89 89 class _GetError(object):
90 90 """Get error from form_errors, and represent it as span wrapped error
91 91 message
92 92
93 93 :param field_name: field to fetch errors for
94 94 :param form_errors: form errors dict
95 95 """
96 96
97 97 def __call__(self, field_name, form_errors):
98 98 tmpl = """<span class="error_msg">%s</span>"""
99 99 if form_errors and form_errors.has_key(field_name):
100 100 return literal(tmpl % form_errors.get(field_name))
101 101
102 102 get_error = _GetError()
103 103
104 104 class _ToolTip(object):
105 105
106 106 def __call__(self, tooltip_title, trim_at=50):
107 107 """Special function just to wrap our text into nice formatted
108 108 autowrapped text
109 109
110 110 :param tooltip_title:
111 111 """
112 112 return escape(tooltip_title)
113 113 tooltip = _ToolTip()
114 114
115 115 class _FilesBreadCrumbs(object):
116 116
117 117 def __call__(self, repo_name, rev, paths):
118 118 if isinstance(paths, str):
119 119 paths = safe_unicode(paths)
120 120 url_l = [link_to(repo_name, url('files_home',
121 121 repo_name=repo_name,
122 122 revision=rev, f_path=''))]
123 123 paths_l = paths.split('/')
124 124 for cnt, p in enumerate(paths_l):
125 125 if p != '':
126 126 url_l.append(link_to(p,
127 127 url('files_home',
128 128 repo_name=repo_name,
129 129 revision=rev,
130 130 f_path='/'.join(paths_l[:cnt + 1])
131 131 )
132 132 )
133 133 )
134 134
135 135 return literal('/'.join(url_l))
136 136
137 137 files_breadcrumbs = _FilesBreadCrumbs()
138 138
139 139 class CodeHtmlFormatter(HtmlFormatter):
140 140 """My code Html Formatter for source codes
141 141 """
142 142
143 143 def wrap(self, source, outfile):
144 144 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
145 145
146 146 def _wrap_code(self, source):
147 147 for cnt, it in enumerate(source):
148 148 i, t = it
149 149 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
150 150 yield i, t
151 151
152 152 def _wrap_tablelinenos(self, inner):
153 153 dummyoutfile = StringIO.StringIO()
154 154 lncount = 0
155 155 for t, line in inner:
156 156 if t:
157 157 lncount += 1
158 158 dummyoutfile.write(line)
159 159
160 160 fl = self.linenostart
161 161 mw = len(str(lncount + fl - 1))
162 162 sp = self.linenospecial
163 163 st = self.linenostep
164 164 la = self.lineanchors
165 165 aln = self.anchorlinenos
166 166 nocls = self.noclasses
167 167 if sp:
168 168 lines = []
169 169
170 170 for i in range(fl, fl + lncount):
171 171 if i % st == 0:
172 172 if i % sp == 0:
173 173 if aln:
174 174 lines.append('<a href="#%s%d" class="special">%*d</a>' %
175 175 (la, i, mw, i))
176 176 else:
177 177 lines.append('<span class="special">%*d</span>' % (mw, i))
178 178 else:
179 179 if aln:
180 180 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
181 181 else:
182 182 lines.append('%*d' % (mw, i))
183 183 else:
184 184 lines.append('')
185 185 ls = '\n'.join(lines)
186 186 else:
187 187 lines = []
188 188 for i in range(fl, fl + lncount):
189 189 if i % st == 0:
190 190 if aln:
191 191 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
192 192 else:
193 193 lines.append('%*d' % (mw, i))
194 194 else:
195 195 lines.append('')
196 196 ls = '\n'.join(lines)
197 197
198 198 # in case you wonder about the seemingly redundant <div> here: since the
199 199 # content in the other cell also is wrapped in a div, some browsers in
200 200 # some configurations seem to mess up the formatting...
201 201 if nocls:
202 202 yield 0, ('<table class="%stable">' % self.cssclass +
203 203 '<tr><td><div class="linenodiv" '
204 204 'style="background-color: #f0f0f0; padding-right: 10px">'
205 205 '<pre style="line-height: 125%">' +
206 206 ls + '</pre></div></td><td id="hlcode" class="code">')
207 207 else:
208 208 yield 0, ('<table class="%stable">' % self.cssclass +
209 209 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
210 210 ls + '</pre></div></td><td id="hlcode" class="code">')
211 211 yield 0, dummyoutfile.getvalue()
212 212 yield 0, '</td></tr></table>'
213 213
214 214
215 215 def pygmentize(filenode, **kwargs):
216 216 """pygmentize function using pygments
217 217
218 218 :param filenode:
219 219 """
220 220
221 221 return literal(code_highlight(filenode.content,
222 222 filenode.lexer, CodeHtmlFormatter(**kwargs)))
223 223
224 224
225 225 def pygmentize_annotation(repo_name, filenode, **kwargs):
226 226 """
227 227 pygmentize function for annotation
228 228
229 229 :param filenode:
230 230 """
231 231
232 232 color_dict = {}
233 233
234 234 def gen_color(n=10000):
235 235 """generator for getting n of evenly distributed colors using
236 236 hsv color and golden ratio. It always return same order of colors
237 237
238 238 :returns: RGB tuple
239 239 """
240 240
241 241 def hsv_to_rgb(h, s, v):
242 242 if s == 0.0:
243 243 return v, v, v
244 244 i = int(h * 6.0) # XXX assume int() truncates!
245 245 f = (h * 6.0) - i
246 246 p = v * (1.0 - s)
247 247 q = v * (1.0 - s * f)
248 248 t = v * (1.0 - s * (1.0 - f))
249 249 i = i % 6
250 250 if i == 0:
251 251 return v, t, p
252 252 if i == 1:
253 253 return q, v, p
254 254 if i == 2:
255 255 return p, v, t
256 256 if i == 3:
257 257 return p, q, v
258 258 if i == 4:
259 259 return t, p, v
260 260 if i == 5:
261 261 return v, p, q
262 262
263 263 golden_ratio = 0.618033988749895
264 264 h = 0.22717784590367374
265 265
266 266 for _ in xrange(n):
267 267 h += golden_ratio
268 268 h %= 1
269 269 HSV_tuple = [h, 0.95, 0.95]
270 270 RGB_tuple = hsv_to_rgb(*HSV_tuple)
271 271 yield map(lambda x: str(int(x * 256)), RGB_tuple)
272 272
273 273 cgenerator = gen_color()
274 274
275 275 def get_color_string(cs):
276 276 if cs in color_dict:
277 277 col = color_dict[cs]
278 278 else:
279 279 col = color_dict[cs] = cgenerator.next()
280 280 return "color: rgb(%s)! important;" % (', '.join(col))
281 281
282 282 def url_func(repo_name):
283 283
284 284 def _url_func(changeset):
285 285 author = changeset.author
286 286 date = changeset.date
287 287 message = tooltip(changeset.message)
288 288
289 289 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
290 290 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
291 291 "</b> %s<br/></div>")
292 292
293 293 tooltip_html = tooltip_html % (author, date, message)
294 294 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
295 295 short_id(changeset.raw_id))
296 296 uri = link_to(
297 297 lnk_format,
298 298 url('changeset_home', repo_name=repo_name,
299 299 revision=changeset.raw_id),
300 300 style=get_color_string(changeset.raw_id),
301 301 class_='tooltip',
302 302 title=tooltip_html
303 303 )
304 304
305 305 uri += '\n'
306 306 return uri
307 307 return _url_func
308 308
309 309 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
310 310
311 311
312 312 def is_following_repo(repo_name, user_id):
313 313 from rhodecode.model.scm import ScmModel
314 314 return ScmModel().is_following_repo(repo_name, user_id)
315 315
316 316 flash = _Flash()
317 317
318 318 #==============================================================================
319 319 # SCM FILTERS available via h.
320 320 #==============================================================================
321 321 from vcs.utils import author_name, author_email
322 322 from rhodecode.lib import credentials_filter, age as _age
323 323 from rhodecode.model.db import User
324 324
325 325 age = lambda x: _age(x)
326 326 capitalize = lambda x: x.capitalize()
327 327 email = author_email
328 328 short_id = lambda x: x[:12]
329 329 hide_credentials = lambda x: ''.join(credentials_filter(x))
330 330
331 331
332 332 def email_or_none(author):
333 333 _email = email(author)
334 334 if _email != '':
335 335 return _email
336 336
337 337 # See if it contains a username we can get an email from
338 338 user = User.get_by_username(author_name(author), case_insensitive=True,
339 339 cache=True)
340 340 if user is not None:
341 341 return user.email
342 342
343 343 # No valid email, not a valid user in the system, none!
344 344 return None
345 345
346 346
347 347 def person(author):
348 348 # attr to return from fetched user
349 349 person_getter = lambda usr: usr.username
350 350
351 351 # Valid email in the attribute passed, see if they're in the system
352 352 _email = email(author)
353 353 if _email != '':
354 354 user = User.get_by_email(_email, case_insensitive=True, cache=True)
355 355 if user is not None:
356 356 return person_getter(user)
357 357 return _email
358 358
359 359 # Maybe it's a username?
360 360 _author = author_name(author)
361 361 user = User.get_by_username(_author, case_insensitive=True,
362 362 cache=True)
363 363 if user is not None:
364 364 return person_getter(user)
365 365
366 366 # Still nothing? Just pass back the author name then
367 367 return _author
368 368
369 369 def bool2icon(value):
370 370 """Returns True/False values represented as small html image of true/false
371 371 icons
372 372
373 373 :param value: bool value
374 374 """
375 375
376 376 if value is True:
377 377 return HTML.tag('img', src=url("/images/icons/accept.png"),
378 378 alt=_('True'))
379 379
380 380 if value is False:
381 381 return HTML.tag('img', src=url("/images/icons/cancel.png"),
382 382 alt=_('False'))
383 383
384 384 return value
385 385
386 386
387 387 def action_parser(user_log, feed=False):
388 388 """This helper will action_map the specified string action into translated
389 389 fancy names with icons and links
390 390
391 391 :param user_log: user log instance
392 392 :param feed: use output for feeds (no html and fancy icons)
393 393 """
394 394
395 395 action = user_log.action
396 396 action_params = ' '
397 397
398 398 x = action.split(':')
399 399
400 400 if len(x) > 1:
401 401 action, action_params = x
402 402
403 403 def get_cs_links():
404 404 revs_limit = 3 #display this amount always
405 405 revs_top_limit = 50 #show upto this amount of changesets hidden
406 406 revs = action_params.split(',')
407 407 repo_name = user_log.repository.repo_name
408 408
409 409 from rhodecode.model.scm import ScmModel
410 410 repo = user_log.repository.scm_instance
411 411
412 412 message = lambda rev: get_changeset_safe(repo, rev).message
413 413 cs_links = []
414 414 cs_links.append(" " + ', '.join ([link_to(rev,
415 415 url('changeset_home',
416 416 repo_name=repo_name,
417 417 revision=rev), title=tooltip(message(rev)),
418 418 class_='tooltip') for rev in revs[:revs_limit] ]))
419 419
420 420 compare_view = (' <div class="compare_view tooltip" title="%s">'
421 421 '<a href="%s">%s</a> '
422 422 '</div>' % (_('Show all combined changesets %s->%s' \
423 423 % (revs[0], revs[-1])),
424 424 url('changeset_home', repo_name=repo_name,
425 425 revision='%s...%s' % (revs[0], revs[-1])
426 426 ),
427 427 _('compare view'))
428 428 )
429 429
430 430 if len(revs) > revs_limit:
431 431 uniq_id = revs[0]
432 432 html_tmpl = ('<span> %s '
433 433 '<a class="show_more" id="_%s" href="#more">%s</a> '
434 434 '%s</span>')
435 435 if not feed:
436 436 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
437 437 % (len(revs) - revs_limit),
438 438 _('revisions')))
439 439
440 440 if not feed:
441 441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 442 else:
443 443 html_tmpl = '<span id="%s"> %s </span>'
444 444
445 445 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
446 446 url('changeset_home',
447 447 repo_name=repo_name, revision=rev),
448 448 title=message(rev), class_='tooltip')
449 449 for rev in revs[revs_limit:revs_top_limit]])))
450 450 if len(revs) > 1:
451 451 cs_links.append(compare_view)
452 452 return ''.join(cs_links)
453 453
454 454 def get_fork_name():
455 455 repo_name = action_params
456 456 return _('fork name ') + str(link_to(action_params, url('summary_home',
457 457 repo_name=repo_name,)))
458 458
459 459 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
460 460 'user_created_repo':(_('[created] repository'), None),
461 461 'user_created_fork':(_('[created] repository as fork'), None),
462 462 'user_forked_repo':(_('[forked] repository'), get_fork_name),
463 463 'user_updated_repo':(_('[updated] repository'), None),
464 464 'admin_deleted_repo':(_('[delete] repository'), None),
465 465 'admin_created_repo':(_('[created] repository'), None),
466 466 'admin_forked_repo':(_('[forked] repository'), None),
467 467 'admin_updated_repo':(_('[updated] repository'), None),
468 468 'push':(_('[pushed] into'), get_cs_links),
469 469 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
470 470 'push_remote':(_('[pulled from remote] into'), get_cs_links),
471 471 'pull':(_('[pulled] from'), None),
472 472 'started_following_repo':(_('[started following] repository'), None),
473 473 'stopped_following_repo':(_('[stopped following] repository'), None),
474 474 }
475 475
476 476 action_str = action_map.get(action, action)
477 477 if feed:
478 478 action = action_str[0].replace('[', '').replace(']', '')
479 479 else:
480 480 action = action_str[0].replace('[', '<span class="journal_highlight">')\
481 481 .replace(']', '</span>')
482 482
483 483 action_params_func = lambda :""
484 484
485 485 if callable(action_str[1]):
486 486 action_params_func = action_str[1]
487 487
488 488 return [literal(action), action_params_func]
489 489
490 490 def action_parser_icon(user_log):
491 491 action = user_log.action
492 492 action_params = None
493 493 x = action.split(':')
494 494
495 495 if len(x) > 1:
496 496 action, action_params = x
497 497
498 498 tmpl = """<img src="%s%s" alt="%s"/>"""
499 499 map = {'user_deleted_repo':'database_delete.png',
500 500 'user_created_repo':'database_add.png',
501 501 'user_created_fork':'arrow_divide.png',
502 502 'user_forked_repo':'arrow_divide.png',
503 503 'user_updated_repo':'database_edit.png',
504 504 'admin_deleted_repo':'database_delete.png',
505 505 'admin_created_repo':'database_add.png',
506 506 'admin_forked_repo':'arrow_divide.png',
507 507 'admin_updated_repo':'database_edit.png',
508 508 'push':'script_add.png',
509 509 'push_local':'script_edit.png',
510 510 'push_remote':'connect.png',
511 511 'pull':'down_16.png',
512 512 'started_following_repo':'heart_add.png',
513 513 'stopped_following_repo':'heart_delete.png',
514 514 }
515 515 return literal(tmpl % ((url('/images/icons/')),
516 516 map.get(action, action), action))
517 517
518 518
519 519 #==============================================================================
520 520 # PERMS
521 521 #==============================================================================
522 522 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
523 523 HasRepoPermissionAny, HasRepoPermissionAll
524 524
525 525 #==============================================================================
526 526 # GRAVATAR URL
527 527 #==============================================================================
528 528
529 529 def gravatar_url(email_address, size=30):
530 530 if (not str2bool(config['app_conf'].get('use_gravatar')) or
531 531 not email_address or email_address == 'anonymous@rhodecode.org'):
532 532 f=lambda a,l:min(l,key=lambda x:abs(x-a))
533 533 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
534 534
535 535 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
536 536 default = 'identicon'
537 537 baseurl_nossl = "http://www.gravatar.com/avatar/"
538 538 baseurl_ssl = "https://secure.gravatar.com/avatar/"
539 539 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
540 540
541 541 if isinstance(email_address, unicode):
542 542 #hashlib crashes on unicode items
543 543 email_address = safe_str(email_address)
544 544 # construct the url
545 545 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
546 546 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
547 547
548 548 return gravatar_url
549 549
550 550
551 551 #==============================================================================
552 552 # REPO PAGER, PAGER FOR REPOSITORY
553 553 #==============================================================================
554 554 class RepoPage(Page):
555 555
556 556 def __init__(self, collection, page=1, items_per_page=20,
557 557 item_count=None, url=None, **kwargs):
558 558
559 559 """Create a "RepoPage" instance. special pager for paging
560 560 repository
561 561 """
562 562 self._url_generator = url
563 563
564 564 # Safe the kwargs class-wide so they can be used in the pager() method
565 565 self.kwargs = kwargs
566 566
567 567 # Save a reference to the collection
568 568 self.original_collection = collection
569 569
570 570 self.collection = collection
571 571
572 572 # The self.page is the number of the current page.
573 573 # The first page has the number 1!
574 574 try:
575 575 self.page = int(page) # make it int() if we get it as a string
576 576 except (ValueError, TypeError):
577 577 self.page = 1
578 578
579 579 self.items_per_page = items_per_page
580 580
581 581 # Unless the user tells us how many items the collections has
582 582 # we calculate that ourselves.
583 583 if item_count is not None:
584 584 self.item_count = item_count
585 585 else:
586 586 self.item_count = len(self.collection)
587 587
588 588 # Compute the number of the first and last available page
589 589 if self.item_count > 0:
590 590 self.first_page = 1
591 591 self.page_count = int(math.ceil(float(self.item_count) /
592 592 self.items_per_page))
593 593 self.last_page = self.first_page + self.page_count - 1
594 594
595 595 # Make sure that the requested page number is the range of
596 596 # valid pages
597 597 if self.page > self.last_page:
598 598 self.page = self.last_page
599 599 elif self.page < self.first_page:
600 600 self.page = self.first_page
601 601
602 602 # Note: the number of items on this page can be less than
603 603 # items_per_page if the last page is not full
604 604 self.first_item = max(0, (self.item_count) - (self.page *
605 605 items_per_page))
606 606 self.last_item = ((self.item_count - 1) - items_per_page *
607 607 (self.page - 1))
608 608
609 609 self.items = list(self.collection[self.first_item:self.last_item + 1])
610 610
611 611 # Links to previous and next page
612 612 if self.page > self.first_page:
613 613 self.previous_page = self.page - 1
614 614 else:
615 615 self.previous_page = None
616 616
617 617 if self.page < self.last_page:
618 618 self.next_page = self.page + 1
619 619 else:
620 620 self.next_page = None
621 621
622 622 # No items available
623 623 else:
624 624 self.first_page = None
625 625 self.page_count = 0
626 626 self.last_page = None
627 627 self.first_item = None
628 628 self.last_item = None
629 629 self.previous_page = None
630 630 self.next_page = None
631 631 self.items = []
632 632
633 633 # This is a subclass of the 'list' type. Initialise the list now.
634 634 list.__init__(self, reversed(self.items))
635 635
636 636
637 637 def changed_tooltip(nodes):
638 638 """
639 639 Generates a html string for changed nodes in changeset page.
640 640 It limits the output to 30 entries
641 641
642 642 :param nodes: LazyNodesGenerator
643 643 """
644 644 if nodes:
645 645 pref = ': <br/> '
646 646 suf = ''
647 647 if len(nodes) > 30:
648 648 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
649 649 return literal(pref + '<br/> '.join([safe_unicode(x.path)
650 650 for x in nodes[:30]]) + suf)
651 651 else:
652 652 return ': ' + _('No Files')
653 653
654 654
655 655
656 656 def repo_link(groups_and_repos):
657 657 """
658 658 Makes a breadcrumbs link to repo within a group
659 659 joins &raquo; on each group to create a fancy link
660 660
661 661 ex::
662 662 group >> subgroup >> repo
663 663
664 664 :param groups_and_repos:
665 665 """
666 666 groups, repo_name = groups_and_repos
667 667
668 668 if not groups:
669 669 return repo_name
670 670 else:
671 671 def make_link(group):
672 672 return link_to(group.name, url('repos_group_home',
673 673 group_name=group.group_name))
674 674 return literal(' &raquo; '.join(map(make_link, groups)) + \
675 675 " &raquo; " + repo_name)
676 676
677 677 def fancy_file_stats(stats):
678 678 """
679 679 Displays a fancy two colored bar for number of added/deleted
680 680 lines of code on file
681 681
682 682 :param stats: two element list of added/deleted lines of code
683 683 """
684 684
685 685 a, d, t = stats[0], stats[1], stats[0] + stats[1]
686 686 width = 100
687 687 unit = float(width) / (t or 1)
688 688
689 689 # needs > 9% of width to be visible or 0 to be hidden
690 690 a_p = max(9, unit * a) if a > 0 else 0
691 691 d_p = max(9, unit * d) if d > 0 else 0
692 692 p_sum = a_p + d_p
693 693
694 694 if p_sum > width:
695 695 #adjust the percentage to be == 100% since we adjusted to 9
696 696 if a_p > d_p:
697 697 a_p = a_p - (p_sum - width)
698 698 else:
699 699 d_p = d_p - (p_sum - width)
700 700
701 701 a_v = a if a > 0 else ''
702 702 d_v = d if d > 0 else ''
703 703
704 704
705 705 def cgen(l_type):
706 706 mapping = {'tr':'top-right-rounded-corner',
707 707 'tl':'top-left-rounded-corner',
708 708 'br':'bottom-right-rounded-corner',
709 709 'bl':'bottom-left-rounded-corner'}
710 710 map_getter = lambda x:mapping[x]
711 711
712 712 if l_type == 'a' and d_v:
713 713 #case when added and deleted are present
714 714 return ' '.join(map(map_getter, ['tl', 'bl']))
715 715
716 716 if l_type == 'a' and not d_v:
717 717 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
718 718
719 719 if l_type == 'd' and a_v:
720 720 return ' '.join(map(map_getter, ['tr', 'br']))
721 721
722 722 if l_type == 'd' and not a_v:
723 723 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
724 724
725 725
726 726
727 727 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
728 728 a_p, a_v)
729 729 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
730 730 d_p, d_v)
731 731 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
732 732
733 733
734 734 def urlify_text(text_):
735 735 import re
736 736
737 737 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
738 738 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
739 739
740 740 def url_func(match_obj):
741 741 url_full = match_obj.groups()[0]
742 742 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
743 743
744 744 return literal(url_pat.sub(url_func, text_))
745 745
746 746 def urlify_changesets(text_, repository):
747 747 import re
748 748 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
749 749
750 750 def url_func(match_obj):
751 751 rev = match_obj.groups()[0]
752 752 pref = ''
753 753 if match_obj.group().startswith(' '):
754 754 pref = ' '
755 755 tmpl = (
756 756 '%(pref)s<a class="%(cls)s" href="%(url)s">'
757 757 '%(rev)s'
758 758 '</a>'
759 759 )
760 760 return tmpl % {
761 761 'pref': pref,
762 762 'cls': 'revision-link',
763 763 'url': url('changeset_home', repo_name=repository, revision=rev),
764 764 'rev': rev,
765 765 }
766 766
767 767 newtext = URL_PAT.sub(url_func, text_)
768 768
769 769 return newtext
770 770
771 771 def urlify_commit(text_, repository=None, link_=None):
772 772 import re
773 773 import traceback
774 774
775 775 # urlify changesets
776 776 text_ = urlify_changesets(text_, repository)
777
777
778 778 def linkify_others(t,l):
779 779 urls = re.compile(r'(\<a.*?\<\/a\>)',)
780 780 links = []
781 781 for e in urls.split(t):
782 782 if not urls.match(e):
783 783 links.append('<a class="message-link" href="%s">%s</a>' % (l,e))
784 784 else:
785 links.append(e)
786
785 links.append(e)
786
787 787 return ''.join(links)
788 788 try:
789 789 conf = config['app_conf']
790 790
791 791 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
792 792
793 793 if URL_PAT:
794 794 ISSUE_SERVER_LNK = conf.get('issue_server_link')
795 795 ISSUE_PREFIX = conf.get('issue_prefix')
796 796
797 797 def url_func(match_obj):
798 798 pref = ''
799 799 if match_obj.group().startswith(' '):
800 800 pref = ' '
801 801
802 802 issue_id = ''.join(match_obj.groups())
803 803 tmpl = (
804 804 '%(pref)s<a class="%(cls)s" href="%(url)s">'
805 805 '%(issue-prefix)s%(id-repr)s'
806 806 '</a>'
807 807 )
808 808 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
809 809 if repository:
810 810 url = url.replace('{repo}', repository)
811 811
812 812 return tmpl % {
813 813 'pref': pref,
814 814 'cls': 'issue-tracker-link',
815 815 'url': url,
816 816 'id-repr': issue_id,
817 817 'issue-prefix': ISSUE_PREFIX,
818 818 'serv': ISSUE_SERVER_LNK,
819 819 }
820
820
821 821 newtext = URL_PAT.sub(url_func, text_)
822
822
823 823 # wrap not links into final link => link_
824 824 newtext = linkify_others(newtext, link_)
825
825
826 826 return literal(newtext)
827 827 except:
828 828 log.error(traceback.format_exc())
829 829 pass
830 830
831 831 return text_
832 832
833 833
834 834 def rst(source):
835 835 return literal('<div class="rst-block">%s</div>' %
836 836 MarkupRenderer.rst(source))
837 837
838 838
839 839 def rst_w_mentions(source):
840 840 """
841 841 Wrapped rst renderer with @mention highlighting
842 842
843 843 :param source:
844 844 """
845 845 return literal('<div class="rst-block">%s</div>' %
846 846 MarkupRenderer.rst_with_mentions(source))
@@ -1,336 +1,336 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 6 <div id="header-inner" class="title">
7 7 <div id="logo">
8 8 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 9 </div>
10 10 <!-- MENU -->
11 11 ${self.page_nav()}
12 12 <!-- END MENU -->
13 13 ${self.body()}
14 14 </div>
15 15 </div>
16 16 <!-- END HEADER -->
17 17
18 18 <!-- CONTENT -->
19 19 <div id="content">
20 20 <div class="flash_msg">
21 21 <% messages = h.flash.pop_messages() %>
22 22 % if messages:
23 23 <ul id="flash-messages">
24 24 % for message in messages:
25 25 <li class="${message.category}_msg">${message}</li>
26 26 % endfor
27 27 </ul>
28 28 % endif
29 29 </div>
30 30 <div id="main">
31 31 ${next.main()}
32 32 </div>
33 33 </div>
34 34 <!-- END CONTENT -->
35 35
36 36 <!-- FOOTER -->
37 37 <div id="footer">
38 38 <div id="footer-inner" class="title">
39 39 <div>
40 40 <p class="footer-link">
41 41 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 42 </p>
43 43 <p class="footer-link-right">
44 44 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
45 45 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 46 </p>
47 47 </div>
48 48 </div>
49 49 </div>
50 50 <!-- END FOOTER -->
51 51
52 52 ### MAKO DEFS ###
53 53 <%def name="page_nav()">
54 54 ${self.menu()}
55 55 </%def>
56 56
57 57 <%def name="breadcrumbs()">
58 58 <div class="breadcrumbs">
59 59 ${self.breadcrumbs_links()}
60 60 </div>
61 61 </%def>
62 62
63 63 <%def name="usermenu()">
64 64 <div class="user-menu">
65 65 <div class="container">
66 66 <div class="gravatar" id="quick_login_link">
67 67 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,24)}" />
68 68 </div>
69 69 %if c.rhodecode_user.username != 'default' and c.unread_notifications != 0:
70 70 <div class="notifications">
71 71 <a id="notification_counter" href="${h.url('notifications')}">${c.unread_notifications}</a>
72 72 </div>
73 73 %endif
74 74 </div>
75 75 <div id="quick_login" style="display:none">
76 76 %if c.rhodecode_user.username == 'default':
77 77 <h4>${_('Login to your account')}</h4>
78 78 ${h.form(h.url('login_home',came_from=h.url.current()))}
79 79 <div class="form">
80 80 <div class="fields">
81 81 <div class="field">
82 82 <div class="label">
83 83 <label for="username">${_('Username')}:</label>
84 84 </div>
85 85 <div class="input">
86 86 ${h.text('username',class_='focus',size=40)}
87 87 </div>
88
88
89 89 </div>
90 90 <div class="field">
91 91 <div class="label">
92 92 <label for="password">${_('Password')}:</label>
93 93 </div>
94 94 <div class="input">
95 95 ${h.password('password',class_='focus',size=40)}
96 96 </div>
97
97
98 98 </div>
99 99 <div class="buttons">
100 100 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
101 101 <div class="register">
102 102 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
103 103 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
104 104 %endif
105 105 </div>
106 106 <div class="submit">
107 107 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
108 108 </div>
109 109 </div>
110 110 </div>
111 111 </div>
112 112 ${h.end_form()}
113 113 %else:
114 114 <div class="links_left">
115 115 <div class="full_name">${c.rhodecode_user.full_name}</div>
116 116 <div class="email">${c.rhodecode_user.email}</div>
117 117 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
118 118 </div>
119 119 <div class="links_right">
120 120 <ol class="links">
121 121 <li>${h.link_to(_(u'Home'),h.url('home'))}</li>
122 122 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
123 123 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
124 124 </ol>
125 125 </div>
126 126 %endif
127 </div>
127 </div>
128 128 </div>
129 129 </%def>
130 130
131 131 <%def name="menu(current=None)">
132 132 <%
133 133 def is_current(selected):
134 134 if selected == current:
135 135 return h.literal('class="current"')
136 136 %>
137 137 %if current not in ['home','admin']:
138 138 ##REGULAR MENU
139 139 <ul id="quick">
140 140 <!-- repo switcher -->
141 141 <li>
142 142 <a class="menu_link" id="repo_switcher" title="${_('Switch repository')}" href="#">
143 143 <span class="icon">
144 144 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
145 145 </span>
146 146 <span>&darr;</span>
147 147 </a>
148 148 <ul id="repo_switcher_list" class="repo_switcher">
149 149 <li>
150 150 <a href="#">${_('loading...')}</a>
151 151 </li>
152 152 </ul>
153 153 </li>
154 154
155 155 <li ${is_current('summary')}>
156 156 <a class="menu_link" title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
157 157 <span class="icon">
158 158 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
159 159 </span>
160 160 <span>${_('Summary')}</span>
161 161 </a>
162 162 </li>
163 163 <li ${is_current('changelog')}>
164 164 <a class="menu_link" title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
165 165 <span class="icon">
166 166 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
167 167 </span>
168 168 <span>${_('Changelog')}</span>
169 169 </a>
170 170 </li>
171 171
172 172 <li ${is_current('switch_to')}>
173 173 <a class="menu_link" id="branch_tag_switcher" title="${_('Switch to')}" href="#">
174 174 <span class="icon">
175 175 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
176 176 </span>
177 177 <span>${_('Switch to')}</span>
178 178 </a>
179 179 <ul id="switch_to_list" class="switch_to">
180 180 <li><a href="#">${_('loading...')}</a></li>
181 181 </ul>
182 182 </li>
183 183 <li ${is_current('files')}>
184 184 <a class="menu_link" title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
185 185 <span class="icon">
186 186 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
187 187 </span>
188 188 <span>${_('Files')}</span>
189 189 </a>
190 190 </li>
191 191
192 192 <li ${is_current('options')}>
193 193 <a class="menu_link" title="${_('Options')}" href="#">
194 194 <span class="icon">
195 195 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
196 196 </span>
197 197 <span>${_('Options')}</span>
198 198 </a>
199 199 <ul>
200 200 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
201 201 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
202 202 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
203 203 %else:
204 204 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
205 205 %endif
206 206 %endif
207 207 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
208 208 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
209 209
210 210 % if h.HasPermissionAll('hg.admin')('access admin main page'):
211 211 <li>
212 212 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
213 213 <%def name="admin_menu()">
214 214 <ul>
215 215 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
216 216 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
217 217 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
218 218 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
219 219 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
220 220 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
221 221 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
222 222 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
223 223 </ul>
224 224 </%def>
225 225
226 226 ${admin_menu()}
227 227 </li>
228 228 % endif
229 229 </ul>
230 230 </li>
231 231
232 232 <li>
233 233 <a class="menu_link" title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
234 234 <span class="icon_short">
235 235 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
236 236 </span>
237 237 <span id="current_followers_count" class="short">${c.repository_followers}</span>
238 238 </a>
239 239 </li>
240 240 <li>
241 241 <a class="menu_link" title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
242 242 <span class="icon_short">
243 243 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
244 244 </span>
245 245 <span class="short">${c.repository_forks}</span>
246 246 </a>
247 247 </li>
248 248 ${usermenu()}
249 249 </ul>
250 250 <script type="text/javascript">
251 251 YUE.on('repo_switcher','mouseover',function(){
252 252 function qfilter(){
253 253 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
254 254 var target = 'q_filter_rs';
255 255 var func = function(node){
256 256 return node.parentNode;
257 257 }
258 258 q_filter(target,nodes,func);
259 259 }
260 260 var loaded = YUD.hasClass('repo_switcher','loaded');
261 261 if(!loaded){
262 262 YUD.addClass('repo_switcher','loaded');
263 263 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
264 264 function(o){qfilter();},
265 265 function(o){YUD.removeClass('repo_switcher','loaded');}
266 266 ,null);
267 267 }
268 268 return false;
269 269 });
270 270
271 271 YUE.on('branch_tag_switcher','mouseover',function(){
272 272 var loaded = YUD.hasClass('branch_tag_switcher','loaded');
273 273 if(!loaded){
274 274 YUD.addClass('branch_tag_switcher','loaded');
275 275 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list',
276 276 function(o){},
277 277 function(o){YUD.removeClass('branch_tag_switcher','loaded');}
278 278 ,null);
279 279 }
280 280 return false;
281 281 });
282 282 </script>
283 283 %else:
284 284 ##ROOT MENU
285 285 <ul id="quick">
286 286 <li>
287 287 <a class="menu_link" title="${_('Home')}" href="${h.url('home')}">
288 288 <span class="icon">
289 289 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
290 290 </span>
291 291 <span>${_('Home')}</span>
292 292 </a>
293 293 </li>
294 294 %if c.rhodecode_user.username != 'default':
295 295 <li>
296 296 <a class="menu_link" title="${_('Journal')}" href="${h.url('journal')}">
297 297 <span class="icon">
298 298 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
299 299 </span>
300 300 <span>${_('Journal')}</span>
301 301 </a>
302 302 </li>
303 303 %else:
304 304 <li>
305 305 <a class="menu_link" title="${_('Public journal')}" href="${h.url('public_journal')}">
306 306 <span class="icon">
307 307 <img src="${h.url('/images/icons/book.png')}" alt="${_('Public journal')}" />
308 308 </span>
309 309 <span>${_('Public journal')}</span>
310 310 </a>
311 311 </li>
312 312 %endif
313 313 <li>
314 314 <a class="menu_link" title="${_('Search')}" href="${h.url('search')}">
315 315 <span class="icon">
316 316 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
317 317 </span>
318 318 <span>${_('Search')}</span>
319 319 </a>
320 320 </li>
321 321
322 322 %if h.HasPermissionAll('hg.admin')('access admin main page'):
323 323 <li ${is_current('admin')}>
324 324 <a class="menu_link" title="${_('Admin')}" href="${h.url('admin_home')}">
325 325 <span class="icon">
326 326 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
327 327 </span>
328 328 <span>${_('Admin')}</span>
329 329 </a>
330 330 ${admin_menu()}
331 331 </li>
332 332 %endif
333 333 ${usermenu()}
334 334 </ul>
335 335 %endif
336 336 </%def>
General Comments 0
You need to be logged in to leave comments. Login now