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