##// END OF EJS Templates
disabled api key for anonymous users, and added api_key to rss/atom links for other users
marcink -
r1122:31e82d87 beta
parent child Browse files
Show More
@@ -1,591 +1,591 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.auth
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 authentication and permission libraries
7 7
8 8 :created_on: Apr 4, 2010
9 9 :copyright: (c) 2010 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 12 # This program is free software; you can redistribute it and/or
13 13 # modify it under the terms of the GNU General Public License
14 14 # as published by the Free Software Foundation; version 2
15 15 # of the License or (at your opinion) any later version of the license.
16 16 #
17 17 # This program is distributed in the hope that it will be useful,
18 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 20 # GNU General Public License for more details.
21 21 #
22 22 # You should have received a copy of the GNU General Public License
23 23 # along with this program; if not, write to the Free Software
24 24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 25 # MA 02110-1301, USA.
26 26
27 27 import random
28 28 import logging
29 29 import traceback
30 30 import hashlib
31 31
32 32 from tempfile import _RandomNameSequence
33 33 from decorator import decorator
34 34
35 35 from pylons import config, session, url, request
36 36 from pylons.controllers.util import abort, redirect
37 37 from pylons.i18n.translation import _
38 38
39 39 from rhodecode import __platform__
40 40
41 41 if __platform__ == 'Windows':
42 42 from hashlib import sha256
43 43 if __platform__ in ('Linux', 'Darwin'):
44 44 import bcrypt
45 45
46 46
47 47 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
48 48 from rhodecode.lib.utils import get_repo_slug
49 49 from rhodecode.lib.auth_ldap import AuthLdap
50 50
51 51 from rhodecode.model import meta
52 52 from rhodecode.model.user import UserModel
53 53 from rhodecode.model.db import Permission
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58 class PasswordGenerator(object):
59 59 """This is a simple class for generating password from
60 60 different sets of characters
61 61 usage:
62 62 passwd_gen = PasswordGenerator()
63 63 #print 8-letter password containing only big and small letters of alphabet
64 64 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
65 65 """
66 66 ALPHABETS_NUM = r'''1234567890'''#[0]
67 67 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
68 68 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
69 69 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
70 70 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
71 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
72 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
74 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
75 75
76 76 def __init__(self, passwd=''):
77 77 self.passwd = passwd
78 78
79 79 def gen_password(self, len, type):
80 80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 81 return self.passwd
82 82
83 83 class RhodeCodeCrypto(object):
84 84
85 85 @classmethod
86 86 def hash_string(cls, str_):
87 87 """
88 88 Cryptographic function used for password hashing based on pybcrypt
89 89 or pycrypto in windows
90 90
91 91 :param password: password to hash
92 92 """
93 93 if __platform__ == 'Windows':
94 94 return sha256(str_).hexdigest()
95 95 elif __platform__ in ('Linux', 'Darwin'):
96 96 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 97 else:
98 98 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
99 99
100 100 @classmethod
101 101 def hash_check(cls, password, hashed):
102 102 """
103 103 Checks matching password with it's hashed value, runs different
104 104 implementation based on platform it runs on
105 105
106 106 :param password: password
107 107 :param hashed: password in hashed form
108 108 """
109 109
110 110 if __platform__ == 'Windows':
111 111 return sha256(password).hexdigest() == hashed
112 112 elif __platform__ in ('Linux', 'Darwin'):
113 113 return bcrypt.hashpw(password, hashed) == hashed
114 114 else:
115 115 raise Exception('Unknown or unsupoprted platform %s' % __platform__)
116 116
117 117
118 118
119 119
120 120
121 121 def get_crypt_password(password):
122 122 return RhodeCodeCrypto.hash_string(password)
123 123
124 124 def check_password(password, hashed):
125 125 return RhodeCodeCrypto.hash_check(password, hashed)
126 126
127 127 def generate_api_key(username, salt=None):
128 128 if salt is None:
129 129 salt = _RandomNameSequence().next()
130 130
131 131 return hashlib.sha1(username + salt).hexdigest()
132 132
133 133 def authfunc(environ, username, password):
134 134 """Dummy authentication function used in Mercurial/Git/ and access control,
135 135
136 136 :param environ: needed only for using in Basic auth
137 137 """
138 138 return authenticate(username, password)
139 139
140 140
141 141 def authenticate(username, password):
142 142 """Authentication function used for access control,
143 143 firstly checks for db authentication then if ldap is enabled for ldap
144 144 authentication, also creates ldap user if not in database
145 145
146 146 :param username: username
147 147 :param password: password
148 148 """
149 149 user_model = UserModel()
150 150 user = user_model.get_by_username(username, cache=False)
151 151
152 152 log.debug('Authenticating user using RhodeCode account')
153 153 if user is not None and not user.ldap_dn:
154 154 if user.active:
155 155
156 156 if user.username == 'default' and user.active:
157 157 log.info('user %s authenticated correctly as anonymous user',
158 158 username)
159 159 return True
160 160
161 161 elif user.username == username and check_password(password, user.password):
162 162 log.info('user %s authenticated correctly', username)
163 163 return True
164 164 else:
165 165 log.warning('user %s is disabled', username)
166 166
167 167 else:
168 168 log.debug('Regular authentication failed')
169 169 user_obj = user_model.get_by_username(username, cache=False,
170 170 case_insensitive=True)
171 171
172 172 if user_obj is not None and not user_obj.ldap_dn:
173 173 log.debug('this user already exists as non ldap')
174 174 return False
175 175
176 176 from rhodecode.model.settings import SettingsModel
177 177 ldap_settings = SettingsModel().get_ldap_settings()
178 178
179 179 #======================================================================
180 180 # FALLBACK TO LDAP AUTH IF ENABLE
181 181 #======================================================================
182 182 if ldap_settings.get('ldap_active', False):
183 183 log.debug("Authenticating user using ldap")
184 184 kwargs = {
185 185 'server':ldap_settings.get('ldap_host', ''),
186 186 'base_dn':ldap_settings.get('ldap_base_dn', ''),
187 187 'port':ldap_settings.get('ldap_port'),
188 188 'bind_dn':ldap_settings.get('ldap_dn_user'),
189 189 'bind_pass':ldap_settings.get('ldap_dn_pass'),
190 190 'use_ldaps':ldap_settings.get('ldap_ldaps'),
191 191 'tls_reqcert':ldap_settings.get('ldap_tls_reqcert'),
192 192 'ldap_filter':ldap_settings.get('ldap_filter'),
193 193 'search_scope':ldap_settings.get('ldap_search_scope'),
194 194 'attr_login':ldap_settings.get('ldap_attr_login'),
195 195 'ldap_version':3,
196 196 }
197 197 log.debug('Checking for ldap authentication')
198 198 try:
199 199 aldap = AuthLdap(**kwargs)
200 200 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username, password)
201 201 log.debug('Got ldap DN response %s', user_dn)
202 202
203 203 user_attrs = {
204 204 'name' : ldap_attrs[ldap_settings.get('ldap_attr_firstname')][0],
205 205 'lastname' : ldap_attrs[ldap_settings.get('ldap_attr_lastname')][0],
206 206 'email' : ldap_attrs[ldap_settings.get('ldap_attr_email')][0],
207 207 }
208 208
209 209 if user_model.create_ldap(username, password, user_dn, user_attrs):
210 210 log.info('created new ldap user %s', username)
211 211
212 212 return True
213 213 except (LdapUsernameError, LdapPasswordError,):
214 214 pass
215 215 except (Exception,):
216 216 log.error(traceback.format_exc())
217 217 pass
218 218 return False
219 219
220 220 class AuthUser(object):
221 221 """
222 222 A simple object that handles all attributes of user in RhodeCode
223 223
224 224 It does lookup based on API key,given user, or user present in session
225 225 Then it fills all required information for such user. It also checks if
226 226 anonymous access is enabled and if so, it returns default user as logged
227 227 in
228 228 """
229 229
230 230 def __init__(self, user_id=None, api_key=None):
231 231
232 232 self.user_id = user_id
233 233 self.api_key = None
234 234
235 235 self.username = 'None'
236 236 self.name = ''
237 237 self.lastname = ''
238 238 self.email = ''
239 239 self.is_authenticated = False
240 240 self.admin = False
241 241 self.permissions = {}
242 242 self._api_key = api_key
243 243 self.propagate_data()
244 244
245 245
246 246 def propagate_data(self):
247 247 user_model = UserModel()
248 248 self.anonymous_user = user_model.get_by_username('default', cache=True)
249 if self._api_key:
249 if self._api_key and self._api_key != self.anonymous_user.api_key:
250 250 #try go get user by api key
251 251 log.debug('Auth User lookup by API KEY %s', self._api_key)
252 252 user_model.fill_data(self, api_key=self._api_key)
253 253 else:
254 254 log.debug('Auth User lookup by USER ID %s', self.user_id)
255 255 if self.user_id is not None and self.user_id != self.anonymous_user.user_id:
256 256 user_model.fill_data(self, user_id=self.user_id)
257 257 else:
258 258 if self.anonymous_user.active is True:
259 259 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
260 260 #then we set this user is logged in
261 261 self.is_authenticated = True
262 262 else:
263 263 self.is_authenticated = False
264 264
265 265 log.debug('Auth User is now %s', self)
266 266 user_model.fill_perms(self)
267 267
268 268 @property
269 269 def is_admin(self):
270 270 return self.admin
271 271
272 272 def __repr__(self):
273 273 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
274 274 self.is_authenticated)
275 275
276 276 def set_authenticated(self, authenticated=True):
277 277
278 278 if self.user_id != self.anonymous_user.user_id:
279 279 self.is_authenticated = authenticated
280 280
281 281
282 282 def set_available_permissions(config):
283 283 """This function will propagate pylons globals with all available defined
284 284 permission given in db. We don't want to check each time from db for new
285 285 permissions since adding a new permission also requires application restart
286 286 ie. to decorate new views with the newly created permission
287 287
288 288 :param config: current pylons config instance
289 289
290 290 """
291 291 log.info('getting information about all available permissions')
292 292 try:
293 293 sa = meta.Session()
294 294 all_perms = sa.query(Permission).all()
295 295 except:
296 296 pass
297 297 finally:
298 298 meta.Session.remove()
299 299
300 300 config['available_permissions'] = [x.permission_name for x in all_perms]
301 301
302 302 #===============================================================================
303 303 # CHECK DECORATORS
304 304 #===============================================================================
305 305 class LoginRequired(object):
306 306 """
307 307 Must be logged in to execute this function else
308 308 redirect to login page
309 309
310 310 :param api_access: if enabled this checks only for valid auth token
311 311 and grants access based on valid token
312 312 """
313 313
314 314 def __init__(self, api_access=False):
315 315 self.api_access = api_access
316 316
317 317 def __call__(self, func):
318 318 return decorator(self.__wrapper, func)
319 319
320 320 def __wrapper(self, func, *fargs, **fkwargs):
321 321 cls = fargs[0]
322 322 user = cls.rhodecode_user
323 323
324 324 api_access_ok = False
325 325 if self.api_access:
326 326 log.debug('Checking API KEY access for %s', cls)
327 327 if user.api_key == request.GET.get('api_key'):
328 328 api_access_ok = True
329 329 else:
330 330 log.debug("API KEY token not valid")
331 331
332 332 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
333 333 if user.is_authenticated or api_access_ok:
334 334 log.debug('user %s is authenticated', user.username)
335 335 return func(*fargs, **fkwargs)
336 336 else:
337 337 log.warn('user %s NOT authenticated', user.username)
338 338
339 339 p = ''
340 340 if request.environ.get('SCRIPT_NAME') != '/':
341 341 p += request.environ.get('SCRIPT_NAME')
342 342
343 343 p += request.environ.get('PATH_INFO')
344 344 if request.environ.get('QUERY_STRING'):
345 345 p += '?' + request.environ.get('QUERY_STRING')
346 346
347 347 log.debug('redirecting to login page with %s', p)
348 348 return redirect(url('login_home', came_from=p))
349 349
350 350 class NotAnonymous(object):
351 351 """Must be logged in to execute this function else
352 352 redirect to login page"""
353 353
354 354 def __call__(self, func):
355 355 return decorator(self.__wrapper, func)
356 356
357 357 def __wrapper(self, func, *fargs, **fkwargs):
358 358 cls = fargs[0]
359 359 self.user = cls.rhodecode_user
360 360
361 361 log.debug('Checking if user is not anonymous @%s', cls)
362 362
363 363 anonymous = self.user.username == 'default'
364 364
365 365 if anonymous:
366 366 p = ''
367 367 if request.environ.get('SCRIPT_NAME') != '/':
368 368 p += request.environ.get('SCRIPT_NAME')
369 369
370 370 p += request.environ.get('PATH_INFO')
371 371 if request.environ.get('QUERY_STRING'):
372 372 p += '?' + request.environ.get('QUERY_STRING')
373 373
374 374 import rhodecode.lib.helpers as h
375 375 h.flash(_('You need to be a registered user to perform this action'),
376 376 category='warning')
377 377 return redirect(url('login_home', came_from=p))
378 378 else:
379 379 return func(*fargs, **fkwargs)
380 380
381 381 class PermsDecorator(object):
382 382 """Base class for controller decorators"""
383 383
384 384 def __init__(self, *required_perms):
385 385 available_perms = config['available_permissions']
386 386 for perm in required_perms:
387 387 if perm not in available_perms:
388 388 raise Exception("'%s' permission is not defined" % perm)
389 389 self.required_perms = set(required_perms)
390 390 self.user_perms = None
391 391
392 392 def __call__(self, func):
393 393 return decorator(self.__wrapper, func)
394 394
395 395
396 396 def __wrapper(self, func, *fargs, **fkwargs):
397 397 cls = fargs[0]
398 398 self.user = cls.rhodecode_user
399 399 self.user_perms = self.user.permissions
400 400 log.debug('checking %s permissions %s for %s %s',
401 401 self.__class__.__name__, self.required_perms, cls,
402 402 self.user)
403 403
404 404 if self.check_permissions():
405 405 log.debug('Permission granted for %s %s', cls, self.user)
406 406 return func(*fargs, **fkwargs)
407 407
408 408 else:
409 409 log.warning('Permission denied for %s %s', cls, self.user)
410 410 #redirect with forbidden ret code
411 411 return abort(403)
412 412
413 413
414 414
415 415 def check_permissions(self):
416 416 """Dummy function for overriding"""
417 417 raise Exception('You have to write this function in child class')
418 418
419 419 class HasPermissionAllDecorator(PermsDecorator):
420 420 """Checks for access permission for all given predicates. All of them
421 421 have to be meet in order to fulfill the request
422 422 """
423 423
424 424 def check_permissions(self):
425 425 if self.required_perms.issubset(self.user_perms.get('global')):
426 426 return True
427 427 return False
428 428
429 429
430 430 class HasPermissionAnyDecorator(PermsDecorator):
431 431 """Checks for access permission for any of given predicates. In order to
432 432 fulfill the request any of predicates must be meet
433 433 """
434 434
435 435 def check_permissions(self):
436 436 if self.required_perms.intersection(self.user_perms.get('global')):
437 437 return True
438 438 return False
439 439
440 440 class HasRepoPermissionAllDecorator(PermsDecorator):
441 441 """Checks for access permission for all given predicates for specific
442 442 repository. All of them have to be meet in order to fulfill the request
443 443 """
444 444
445 445 def check_permissions(self):
446 446 repo_name = get_repo_slug(request)
447 447 try:
448 448 user_perms = set([self.user_perms['repositories'][repo_name]])
449 449 except KeyError:
450 450 return False
451 451 if self.required_perms.issubset(user_perms):
452 452 return True
453 453 return False
454 454
455 455
456 456 class HasRepoPermissionAnyDecorator(PermsDecorator):
457 457 """Checks for access permission for any of given predicates for specific
458 458 repository. In order to fulfill the request any of predicates must be meet
459 459 """
460 460
461 461 def check_permissions(self):
462 462 repo_name = get_repo_slug(request)
463 463
464 464 try:
465 465 user_perms = set([self.user_perms['repositories'][repo_name]])
466 466 except KeyError:
467 467 return False
468 468 if self.required_perms.intersection(user_perms):
469 469 return True
470 470 return False
471 471 #===============================================================================
472 472 # CHECK FUNCTIONS
473 473 #===============================================================================
474 474
475 475 class PermsFunction(object):
476 476 """Base function for other check functions"""
477 477
478 478 def __init__(self, *perms):
479 479 available_perms = config['available_permissions']
480 480
481 481 for perm in perms:
482 482 if perm not in available_perms:
483 483 raise Exception("'%s' permission in not defined" % perm)
484 484 self.required_perms = set(perms)
485 485 self.user_perms = None
486 486 self.granted_for = ''
487 487 self.repo_name = None
488 488
489 489 def __call__(self, check_Location=''):
490 490 user = session.get('rhodecode_user', False)
491 491 if not user:
492 492 return False
493 493 self.user_perms = user.permissions
494 494 self.granted_for = user
495 495 log.debug('checking %s %s %s', self.__class__.__name__,
496 496 self.required_perms, user)
497 497
498 498 if self.check_permissions():
499 499 log.debug('Permission granted %s @ %s', self.granted_for,
500 500 check_Location or 'unspecified location')
501 501 return True
502 502
503 503 else:
504 504 log.warning('Permission denied for %s @ %s', self.granted_for,
505 505 check_Location or 'unspecified location')
506 506 return False
507 507
508 508 def check_permissions(self):
509 509 """Dummy function for overriding"""
510 510 raise Exception('You have to write this function in child class')
511 511
512 512 class HasPermissionAll(PermsFunction):
513 513 def check_permissions(self):
514 514 if self.required_perms.issubset(self.user_perms.get('global')):
515 515 return True
516 516 return False
517 517
518 518 class HasPermissionAny(PermsFunction):
519 519 def check_permissions(self):
520 520 if self.required_perms.intersection(self.user_perms.get('global')):
521 521 return True
522 522 return False
523 523
524 524 class HasRepoPermissionAll(PermsFunction):
525 525
526 526 def __call__(self, repo_name=None, check_Location=''):
527 527 self.repo_name = repo_name
528 528 return super(HasRepoPermissionAll, self).__call__(check_Location)
529 529
530 530 def check_permissions(self):
531 531 if not self.repo_name:
532 532 self.repo_name = get_repo_slug(request)
533 533
534 534 try:
535 535 self.user_perms = set([self.user_perms['repositories']\
536 536 [self.repo_name]])
537 537 except KeyError:
538 538 return False
539 539 self.granted_for = self.repo_name
540 540 if self.required_perms.issubset(self.user_perms):
541 541 return True
542 542 return False
543 543
544 544 class HasRepoPermissionAny(PermsFunction):
545 545
546 546 def __call__(self, repo_name=None, check_Location=''):
547 547 self.repo_name = repo_name
548 548 return super(HasRepoPermissionAny, self).__call__(check_Location)
549 549
550 550 def check_permissions(self):
551 551 if not self.repo_name:
552 552 self.repo_name = get_repo_slug(request)
553 553
554 554 try:
555 555 self.user_perms = set([self.user_perms['repositories']\
556 556 [self.repo_name]])
557 557 except KeyError:
558 558 return False
559 559 self.granted_for = self.repo_name
560 560 if self.required_perms.intersection(self.user_perms):
561 561 return True
562 562 return False
563 563
564 564 #===============================================================================
565 565 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
566 566 #===============================================================================
567 567
568 568 class HasPermissionAnyMiddleware(object):
569 569 def __init__(self, *perms):
570 570 self.required_perms = set(perms)
571 571
572 572 def __call__(self, user, repo_name):
573 573 usr = AuthUser(user.user_id)
574 574 try:
575 575 self.user_perms = set([usr.permissions['repositories'][repo_name]])
576 576 except:
577 577 self.user_perms = set()
578 578 self.granted_for = ''
579 579 self.username = user.username
580 580 self.repo_name = repo_name
581 581 return self.check_permissions()
582 582
583 583 def check_permissions(self):
584 584 log.debug('checking mercurial protocol '
585 585 'permissions %s for user:%s repository:%s', self.user_perms,
586 586 self.username, self.repo_name)
587 587 if self.required_perms.intersection(self.user_perms):
588 588 log.debug('permission granted')
589 589 return True
590 590 log.debug('permission denied')
591 591 return False
@@ -1,168 +1,176 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Dashboard')} - ${c.rhodecode_name}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 7 ${c.rhodecode_name}
8 8 </%def>
9 9 <%def name="page_nav()">
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="main()">
13 13 <%def name="get_sort(name)">
14 14 <%name_slug = name.lower().replace(' ','_') %>
15 15
16 16 %if name_slug == c.sort_slug:
17 17 %if c.sort_by.startswith('-'):
18 18 <a href="?sort=${name_slug}">${name}&uarr;</a>
19 19 %else:
20 20 <a href="?sort=-${name_slug}">${name}&darr;</a>
21 21 %endif:
22 22 %else:
23 23 <a href="?sort=${name_slug}">${name}</a>
24 24 %endif
25 25 </%def>
26 26
27 27 <div class="box">
28 28 <!-- box / title -->
29 29 <div class="title">
30 30 <h5>${_('Dashboard')}
31 31 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
32 32 </h5>
33 33 %if c.rhodecode_user.username != 'default':
34 34 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
35 35 <ul class="links">
36 36 <li>
37 37 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
38 38 </li>
39 39 </ul>
40 40 %endif
41 41 %endif
42 42 </div>
43 43 <!-- end box / title -->
44 44 <div class="table">
45 45 <table>
46 46 <thead>
47 47 <tr>
48 48 <th class="left">${get_sort(_('Name'))}</th>
49 49 <th class="left">${get_sort(_('Description'))}</th>
50 50 <th class="left">${get_sort(_('Last change'))}</th>
51 51 <th class="left">${get_sort(_('Tip'))}</th>
52 52 <th class="left">${get_sort(_('Owner'))}</th>
53 53 <th class="left">${_('RSS')}</th>
54 54 <th class="left">${_('Atom')}</th>
55 55 </tr>
56 56 </thead>
57 57 <tbody>
58 58 %for cnt,repo in enumerate(c.repos_list):
59 59 <tr class="parity${cnt%2}">
60 60 <td>
61 61 <div style="white-space: nowrap">
62 62 ## TYPE OF REPO
63 63 %if repo['dbrepo']['repo_type'] =='hg':
64 64 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
65 65 %elif repo['dbrepo']['repo_type'] =='git':
66 66 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
67 67 %else:
68 68
69 69 %endif
70 70
71 71 ##PRIVATE/PUBLIC
72 72 %if repo['dbrepo']['private']:
73 73 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
74 74 %else:
75 75 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
76 76 %endif
77 77
78 78 ##NAME
79 79 ${h.link_to(repo['name'],
80 80 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
81 81 %if repo['dbrepo_fork']:
82 82 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
83 83 <img class="icon" alt="${_('fork')}"
84 84 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
85 85 src="${h.url("/images/icons/arrow_divide.png")}"/></a>
86 86 %endif
87 87 </div>
88 88 </td>
89 89 ##DESCRIPTION
90 90 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
91 91 ${h.truncate(repo['description'],60)}</span>
92 92 </td>
93 93 ##LAST CHANGE
94 94 <td>
95 95 <span class="tooltip" title="${repo['last_change']}">
96 96 ${h.age(repo['last_change'])}</span>
97 97 </td>
98 98 <td>
99 99 %if repo['rev']>=0:
100 100 ${h.link_to('r%s:%s' % (repo['rev'],h.short_id(repo['tip'])),
101 101 h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']),
102 102 class_="tooltip",
103 103 title=h.tooltip(repo['last_msg']))}
104 104 %else:
105 105 ${_('No changesets yet')}
106 106 %endif
107 107 </td>
108 108 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
109 109 <td>
110 %if c.rhodecode_user.username != 'default':
111 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
112 %else:
110 113 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
114 %endif:
111 115 </td>
112 116 <td>
117 %if c.rhodecode_user.username != 'default':
118 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
119 %else:
113 120 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
121 %endif:
114 122 </td>
115 123 </tr>
116 124 %endfor
117 125 </tbody>
118 126 </table>
119 127 </div>
120 128 </div>
121 129
122 130
123 131 <script type="text/javascript">
124 132 var D = YAHOO.util.Dom;
125 133 var E = YAHOO.util.Event;
126 134 var S = YAHOO.util.Selector;
127 135
128 136 var q_filter = D.get('q_filter');
129 137 var F = YAHOO.namespace('q_filter');
130 138
131 139 E.on(q_filter,'click',function(){
132 140 q_filter.value = '';
133 141 });
134 142
135 143 F.filterTimeout = null;
136 144
137 145 F.updateFilter = function() {
138 146 // Reset timeout
139 147 F.filterTimeout = null;
140 148
141 149 var obsolete = [];
142 150 var nodes = S.query('div.table tr td div a.repo_name');
143 151 var req = D.get('q_filter').value;
144 152 for (n in nodes){
145 153 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
146 154 }
147 155 if (req){
148 156 for (n in nodes){
149 157 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
150 158 obsolete.push(nodes[n]);
151 159 }
152 160 }
153 161 if(obsolete){
154 162 for (n in obsolete){
155 163 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
156 164 }
157 165 }
158 166 }
159 167 }
160 168
161 169 E.on(q_filter,'keyup',function(e){
162 170 clearTimeout(F.filterTimeout);
163 171 setTimeout(F.updateFilter,600);
164 172 });
165 173
166 174 </script>
167 175
168 176 </%def>
@@ -1,684 +1,689 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(u'Home',h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="main()">
20 20 <div class="box box-left">
21 21 <!-- box / title -->
22 22 <div class="title">
23 23 ${self.breadcrumbs()}
24 24 </div>
25 25 <!-- end box / title -->
26 26 <div class="form">
27 27 <div class="fields">
28 28
29 29 <div class="field">
30 30 <div class="label">
31 31 <label>${_('Name')}:</label>
32 32 </div>
33 33 <div class="input-short">
34 34 %if c.dbrepo.repo_type =='hg':
35 35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
36 36 %endif
37 37 %if c.dbrepo.repo_type =='git':
38 38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
39 39 %endif
40 40
41 41 %if c.dbrepo.private:
42 42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
43 43 %else:
44 44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
45 45 %endif
46 46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo.name}</span>
47 47 %if c.rhodecode_user.username != 'default':
48 48 %if c.following:
49 49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
50 50 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
51 51 </span>
52 52 %else:
53 53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
54 54 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
55 55 </span>
56 56 %endif
57 57 %endif:
58 58 <br/>
59 59 %if c.dbrepo.fork:
60 60 <span style="margin-top:5px">
61 61 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
62 62 <img class="icon" alt="${_('public')}"
63 63 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
64 64 src="${h.url("/images/icons/arrow_divide.png")}"/>
65 65 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
66 66 </a>
67 67 </span>
68 68 %endif
69 69 %if c.dbrepo.clone_uri:
70 70 <span style="margin-top:5px">
71 71 <a href="${h.url(str(c.dbrepo.clone_uri))}">
72 72 <img class="icon" alt="${_('remote clone')}"
73 73 title="${_('Clone from')} ${c.dbrepo.clone_uri}"
74 74 src="${h.url("/images/icons/connect.png")}"/>
75 75 ${_('Clone from')} ${c.dbrepo.clone_uri}
76 76 </a>
77 77 </span>
78 78 %endif
79 79 </div>
80 80 </div>
81 81
82 82
83 83 <div class="field">
84 84 <div class="label">
85 85 <label>${_('Description')}:</label>
86 86 </div>
87 87 <div class="input-short">
88 88 ${c.dbrepo.description}
89 89 </div>
90 90 </div>
91 91
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label>${_('Contact')}:</label>
96 96 </div>
97 97 <div class="input-short">
98 98 <div class="gravatar">
99 99 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
100 100 </div>
101 101 ${_('Username')}: ${c.dbrepo.user.username}<br/>
102 102 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
103 103 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
104 104 </div>
105 105 </div>
106 106
107 107 <div class="field">
108 108 <div class="label">
109 109 <label>${_('Last change')}:</label>
110 110 </div>
111 111 <div class="input-short">
112 112 ${h.age(c.repo.last_change)} - ${c.repo.last_change}
113 113 ${_('by')} ${h.get_changeset_safe(c.repo,'tip').author}
114 114
115 115 </div>
116 116 </div>
117 117
118 118 <div class="field">
119 119 <div class="label">
120 120 <label>${_('Clone url')}:</label>
121 121 </div>
122 122 <div class="input-short">
123 123 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
124 124 </div>
125 125 </div>
126 126
127 127 <div class="field">
128 128 <div class="label">
129 129 <label>${_('Trending source files')}:</label>
130 130 </div>
131 131 <div class="input-short">
132 132 <div id="lang_stats"></div>
133 133 </div>
134 134 </div>
135 135
136 136 <div class="field">
137 137 <div class="label">
138 138 <label>${_('Download')}:</label>
139 139 </div>
140 140 <div class="input-short">
141 141 %if len(c.repo.revisions) == 0:
142 142 ${_('There are no downloads yet')}
143 143 %elif c.enable_downloads is False:
144 144 ${_('Downloads are disabled for this repository')}
145 145 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
146 146 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
147 147 %endif
148 148 %else:
149 149 ${h.select('download_options',c.repo.get_changeset().raw_id,c.download_options)}
150 150 %for cnt,archive in enumerate(c.repo._get_archives()):
151 151 %if cnt >=1:
152 152 |
153 153 %endif
154 154 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
155 155 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
156 156 h.url('files_archive_home',repo_name=c.repo.name,
157 157 fname='tip'+archive['extension']),class_="archive_icon")}</span>
158 158 %endfor
159 159 %endif
160 160 </div>
161 161 </div>
162 162
163 163 <div class="field">
164 164 <div class="label">
165 165 <label>${_('Feeds')}:</label>
166 166 </div>
167 167 <div class="input-short">
168 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name),class_='rss_icon')}
168 %if c.rhodecode_user.username != 'default':
169 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
170 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
171 %else:
172 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name,class_='rss_icon')}
169 173 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name),class_='atom_icon')}
174 %endif
170 175 </div>
171 176 </div>
172 177 </div>
173 178 </div>
174 179 <script type="text/javascript">
175 180 YUE.onDOMReady(function(e){
176 181 id = 'clone_url';
177 182 YUE.on(id,'click',function(e){
178 183 YUD.get('clone_url').select();
179 184 })
180 185 })
181 186 var data = ${c.trending_languages|n};
182 187 var total = 0;
183 188 var no_data = true;
184 189 for (k in data){
185 190 total += data[k];
186 191 no_data = false;
187 192 }
188 193 var tbl = document.createElement('table');
189 194 tbl.setAttribute('class','trending_language_tbl');
190 195 var cnt =0;
191 196 for (k in data){
192 197 cnt+=1;
193 198 var hide = cnt>2;
194 199 var tr = document.createElement('tr');
195 200 if (hide){
196 201 tr.setAttribute('style','display:none');
197 202 tr.setAttribute('class','stats_hidden');
198 203 }
199 204 var percentage = Math.round((data[k]/total*100),2);
200 205 var value = data[k];
201 206 var td1 = document.createElement('td');
202 207 td1.width=150;
203 208 var trending_language_label = document.createElement('div');
204 209 trending_language_label.innerHTML = k;
205 210 td1.appendChild(trending_language_label);
206 211
207 212 var td2 = document.createElement('td');
208 213 td2.setAttribute('style','padding-right:14px !important');
209 214 var trending_language = document.createElement('div');
210 215 var nr_files = value+" ${_('files')}";
211 216
212 217 trending_language.title = k+" "+nr_files;
213 218
214 219 if (percentage>20){
215 220 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
216 221 }
217 222 else{
218 223 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
219 224 }
220 225
221 226 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
222 227 trending_language.style.width=percentage+"%";
223 228 td2.appendChild(trending_language);
224 229
225 230 tr.appendChild(td1);
226 231 tr.appendChild(td2);
227 232 tbl.appendChild(tr);
228 233 if(cnt == 2){
229 234 var show_more = document.createElement('tr');
230 235 var td=document.createElement('td');
231 236 lnk = document.createElement('a');
232 237 lnk.href='#';
233 238 lnk.innerHTML = "${_("show more")}";
234 239 lnk.id='code_stats_show_more';
235 240 td.appendChild(lnk);
236 241 show_more.appendChild(td);
237 242 show_more.appendChild(document.createElement('td'));
238 243 tbl.appendChild(show_more);
239 244 }
240 245
241 246 }
242 247 if(no_data){
243 248 var tr = document.createElement('tr');
244 249 var td1 = document.createElement('td');
245 250 td1.innerHTML = "${c.no_data_msg}";
246 251 tr.appendChild(td1);
247 252 tbl.appendChild(tr);
248 253 }
249 254 YUD.get('lang_stats').appendChild(tbl);
250 255 YUE.on('code_stats_show_more','click',function(){
251 256 l = YUD.getElementsByClassName('stats_hidden')
252 257 for (e in l){
253 258 YUD.setStyle(l[e],'display','');
254 259 };
255 260 YUD.setStyle(YUD.get('code_stats_show_more'),
256 261 'display','none');
257 262 })
258 263
259 264
260 265 YUE.on('download_options','change',function(e){
261 266 var new_cs = e.target.options[e.target.selectedIndex];
262 267 var tmpl_links = {}
263 268 %for cnt,archive in enumerate(c.repo._get_archives()):
264 269 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
265 270 h.url('files_archive_home',repo_name=c.repo.name,
266 271 fname='__CS__'+archive['extension']),class_="archive_icon")}';
267 272 %endfor
268 273
269 274
270 275 for(k in tmpl_links){
271 276 var s = YUD.get(k+'_link')
272 277 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
273 278 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
274 279 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
275 280 }
276 281
277 282 })
278 283
279 284 </script>
280 285 </div>
281 286
282 287 <div class="box box-right" style="min-height:455px">
283 288 <!-- box / title -->
284 289 <div class="title">
285 290 <h5>${_('Commit activity by day / author')}</h5>
286 291 </div>
287 292
288 293 <div class="table">
289 294 %if c.no_data:
290 295 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
291 296 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
292 297 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
293 298 %endif
294 299 </div>
295 300 %endif:
296 301 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
297 302 <div style="clear: both;height: 10px"></div>
298 303 <div id="overview" style="width:460px;height:100px;float:left"></div>
299 304
300 305 <div id="legend_data" style="clear:both;margin-top:10px;">
301 306 <div id="legend_container"></div>
302 307 <div id="legend_choices">
303 308 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
304 309 </div>
305 310 </div>
306 311 <script type="text/javascript">
307 312 /**
308 313 * Plots summary graph
309 314 *
310 315 * @class SummaryPlot
311 316 * @param {from} initial from for detailed graph
312 317 * @param {to} initial to for detailed graph
313 318 * @param {dataset}
314 319 * @param {overview_dataset}
315 320 */
316 321 function SummaryPlot(from,to,dataset,overview_dataset) {
317 322 var initial_ranges = {
318 323 "xaxis":{
319 324 "from":from,
320 325 "to":to,
321 326 },
322 327 };
323 328 var dataset = dataset;
324 329 var overview_dataset = [overview_dataset];
325 330 var choiceContainer = YUD.get("legend_choices");
326 331 var choiceContainerTable = YUD.get("legend_choices_tables");
327 332 var plotContainer = YUD.get('commit_history');
328 333 var overviewContainer = YUD.get('overview');
329 334
330 335 var plot_options = {
331 336 bars: {show:true,align:'center',lineWidth:4},
332 337 legend: {show:true, container:"legend_container"},
333 338 points: {show:true,radius:0,fill:false},
334 339 yaxis: {tickDecimals:0,},
335 340 xaxis: {
336 341 mode: "time",
337 342 timeformat: "%d/%m",
338 343 min:from,
339 344 max:to,
340 345 },
341 346 grid: {
342 347 hoverable: true,
343 348 clickable: true,
344 349 autoHighlight:true,
345 350 color: "#999"
346 351 },
347 352 //selection: {mode: "x"}
348 353 };
349 354 var overview_options = {
350 355 legend:{show:false},
351 356 bars: {show:true,barWidth: 2,},
352 357 shadowSize: 0,
353 358 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
354 359 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
355 360 grid: {color: "#999",},
356 361 selection: {mode: "x"}
357 362 };
358 363
359 364 /**
360 365 *get dummy data needed in few places
361 366 */
362 367 function getDummyData(label){
363 368 return {"label":label,
364 369 "data":[{"time":0,
365 370 "commits":0,
366 371 "added":0,
367 372 "changed":0,
368 373 "removed":0,
369 374 }],
370 375 "schema":["commits"],
371 376 "color":'#ffffff',
372 377 }
373 378 }
374 379
375 380 /**
376 381 * generate checkboxes accordindly to data
377 382 * @param keys
378 383 * @returns
379 384 */
380 385 function generateCheckboxes(data) {
381 386 //append checkboxes
382 387 var i = 0;
383 388 choiceContainerTable.innerHTML = '';
384 389 for(var pos in data) {
385 390
386 391 data[pos].color = i;
387 392 i++;
388 393 if(data[pos].label != ''){
389 394 choiceContainerTable.innerHTML += '<tr><td>'+
390 395 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
391 396 +data[pos].label+
392 397 '</td></tr>';
393 398 }
394 399 }
395 400 }
396 401
397 402 /**
398 403 * ToolTip show
399 404 */
400 405 function showTooltip(x, y, contents) {
401 406 var div=document.getElementById('tooltip');
402 407 if(!div) {
403 408 div = document.createElement('div');
404 409 div.id="tooltip";
405 410 div.style.position="absolute";
406 411 div.style.border='1px solid #fdd';
407 412 div.style.padding='2px';
408 413 div.style.backgroundColor='#fee';
409 414 document.body.appendChild(div);
410 415 }
411 416 YUD.setStyle(div, 'opacity', 0);
412 417 div.innerHTML = contents;
413 418 div.style.top=(y + 5) + "px";
414 419 div.style.left=(x + 5) + "px";
415 420
416 421 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
417 422 anim.animate();
418 423 }
419 424
420 425 /**
421 426 * This function will detect if selected period has some changesets
422 427 for this user if it does this data is then pushed for displaying
423 428 Additionally it will only display users that are selected by the checkbox
424 429 */
425 430 function getDataAccordingToRanges(ranges) {
426 431
427 432 var data = [];
428 433 var keys = [];
429 434 for(var key in dataset){
430 435 var push = false;
431 436
432 437 //method1 slow !!
433 438 //*
434 439 for(var ds in dataset[key].data){
435 440 commit_data = dataset[key].data[ds];
436 441 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
437 442 push = true;
438 443 break;
439 444 }
440 445 }
441 446 //*/
442 447
443 448 /*//method2 sorted commit data !!!
444 449
445 450 var first_commit = dataset[key].data[0].time;
446 451 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
447 452
448 453 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
449 454 push = true;
450 455 }
451 456 //*/
452 457
453 458 if(push){
454 459 data.push(dataset[key]);
455 460 }
456 461 }
457 462 if(data.length >= 1){
458 463 return data;
459 464 }
460 465 else{
461 466 //just return dummy data for graph to plot itself
462 467 return [getDummyData('')];
463 468 }
464 469
465 470 }
466 471
467 472 /**
468 473 * redraw using new checkbox data
469 474 */
470 475 function plotchoiced(e,args){
471 476 var cur_data = args[0];
472 477 var cur_ranges = args[1];
473 478
474 479 var new_data = [];
475 480 var inputs = choiceContainer.getElementsByTagName("input");
476 481
477 482 //show only checked labels
478 483 for(var i=0; i<inputs.length; i++) {
479 484 var checkbox_key = inputs[i].name;
480 485
481 486 if(inputs[i].checked){
482 487 for(var d in cur_data){
483 488 if(cur_data[d].label == checkbox_key){
484 489 new_data.push(cur_data[d]);
485 490 }
486 491 }
487 492 }
488 493 else{
489 494 //push dummy data to not hide the label
490 495 new_data.push(getDummyData(checkbox_key));
491 496 }
492 497 }
493 498
494 499 var new_options = YAHOO.lang.merge(plot_options, {
495 500 xaxis: {
496 501 min: cur_ranges.xaxis.from,
497 502 max: cur_ranges.xaxis.to,
498 503 mode:"time",
499 504 timeformat: "%d/%m",
500 505 },
501 506 });
502 507 if (!new_data){
503 508 new_data = [[0,1]];
504 509 }
505 510 // do the zooming
506 511 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
507 512
508 513 plot.subscribe("plotselected", plotselected);
509 514
510 515 //resubscribe plothover
511 516 plot.subscribe("plothover", plothover);
512 517
513 518 // don't fire event on the overview to prevent eternal loop
514 519 overview.setSelection(cur_ranges, true);
515 520
516 521 }
517 522
518 523 /**
519 524 * plot only selected items from overview
520 525 * @param ranges
521 526 * @returns
522 527 */
523 528 function plotselected(ranges,cur_data) {
524 529 //updates the data for new plot
525 530 data = getDataAccordingToRanges(ranges);
526 531 generateCheckboxes(data);
527 532
528 533 var new_options = YAHOO.lang.merge(plot_options, {
529 534 xaxis: {
530 535 min: ranges.xaxis.from,
531 536 max: ranges.xaxis.to,
532 537 mode:"time",
533 538 timeformat: "%d/%m",
534 539 },
535 540 yaxis: {
536 541 min: ranges.yaxis.from,
537 542 max: ranges.yaxis.to,
538 543 },
539 544
540 545 });
541 546 // do the zooming
542 547 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
543 548
544 549 plot.subscribe("plotselected", plotselected);
545 550
546 551 //resubscribe plothover
547 552 plot.subscribe("plothover", plothover);
548 553
549 554 // don't fire event on the overview to prevent eternal loop
550 555 overview.setSelection(ranges, true);
551 556
552 557 //resubscribe choiced
553 558 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
554 559 }
555 560
556 561 var previousPoint = null;
557 562
558 563 function plothover(o) {
559 564 var pos = o.pos;
560 565 var item = o.item;
561 566
562 567 //YUD.get("x").innerHTML = pos.x.toFixed(2);
563 568 //YUD.get("y").innerHTML = pos.y.toFixed(2);
564 569 if (item) {
565 570 if (previousPoint != item.datapoint) {
566 571 previousPoint = item.datapoint;
567 572
568 573 var tooltip = YUD.get("tooltip");
569 574 if(tooltip) {
570 575 tooltip.parentNode.removeChild(tooltip);
571 576 }
572 577 var x = item.datapoint.x.toFixed(2);
573 578 var y = item.datapoint.y.toFixed(2);
574 579
575 580 if (!item.series.label){
576 581 item.series.label = 'commits';
577 582 }
578 583 var d = new Date(x*1000);
579 584 var fd = d.toDateString()
580 585 var nr_commits = parseInt(y);
581 586
582 587 var cur_data = dataset[item.series.label].data[item.dataIndex];
583 588 var added = cur_data.added;
584 589 var changed = cur_data.changed;
585 590 var removed = cur_data.removed;
586 591
587 592 var nr_commits_suffix = " ${_('commits')} ";
588 593 var added_suffix = " ${_('files added')} ";
589 594 var changed_suffix = " ${_('files changed')} ";
590 595 var removed_suffix = " ${_('files removed')} ";
591 596
592 597
593 598 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
594 599 if(added==1){added_suffix=" ${_('file added')} ";}
595 600 if(changed==1){changed_suffix=" ${_('file changed')} ";}
596 601 if(removed==1){removed_suffix=" ${_('file removed')} ";}
597 602
598 603 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
599 604 +'<br/>'+
600 605 nr_commits + nr_commits_suffix+'<br/>'+
601 606 added + added_suffix +'<br/>'+
602 607 changed + changed_suffix + '<br/>'+
603 608 removed + removed_suffix + '<br/>');
604 609 }
605 610 }
606 611 else {
607 612 var tooltip = YUD.get("tooltip");
608 613
609 614 if(tooltip) {
610 615 tooltip.parentNode.removeChild(tooltip);
611 616 }
612 617 previousPoint = null;
613 618 }
614 619 }
615 620
616 621 /**
617 622 * MAIN EXECUTION
618 623 */
619 624
620 625 var data = getDataAccordingToRanges(initial_ranges);
621 626 generateCheckboxes(data);
622 627
623 628 //main plot
624 629 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
625 630
626 631 //overview
627 632 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
628 633
629 634 //show initial selection on overview
630 635 overview.setSelection(initial_ranges);
631 636
632 637 plot.subscribe("plotselected", plotselected);
633 638
634 639 overview.subscribe("plotselected", function (ranges) {
635 640 plot.setSelection(ranges);
636 641 });
637 642
638 643 plot.subscribe("plothover", plothover);
639 644
640 645 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
641 646 }
642 647 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
643 648 </script>
644 649
645 650 </div>
646 651 </div>
647 652
648 653 <div class="box">
649 654 <div class="title">
650 655 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
651 656 </div>
652 657 <div class="table">
653 658 <div id="shortlog_data">
654 659 <%include file='../shortlog/shortlog_data.html'/>
655 660 </div>
656 661 ##%if c.repo_changesets:
657 662 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
658 663 ##%endif
659 664 </div>
660 665 </div>
661 666 <div class="box">
662 667 <div class="title">
663 668 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
664 669 </div>
665 670 <div class="table">
666 671 <%include file='../tags/tags_data.html'/>
667 672 %if c.repo_changesets:
668 673 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
669 674 %endif
670 675 </div>
671 676 </div>
672 677 <div class="box">
673 678 <div class="title">
674 679 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
675 680 </div>
676 681 <div class="table">
677 682 <%include file='../branches/branches_data.html'/>
678 683 %if c.repo_changesets:
679 684 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
680 685 %endif
681 686 </div>
682 687 </div>
683 688
684 689 </%def>
General Comments 0
You need to be logged in to leave comments. Login now