##// END OF EJS Templates
Full IP restrictions enabled...
marcink -
r3146:c5169e44 beta
parent child Browse files
Show More
@@ -1,296 +1,294 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 JSON RPC controller
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import inspect
29 29 import logging
30 30 import types
31 31 import urllib
32 32 import traceback
33 33 import time
34 34
35 35 from rhodecode.lib.compat import izip_longest, json
36 36
37 37 from paste.response import replace_header
38 38
39 39 from pylons.controllers import WSGIController
40 40
41 41
42 42 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
43 43 HTTPBadRequest, HTTPError
44 44
45 45 from rhodecode.model.db import User
46 from rhodecode.lib.auth import AuthUser, check_ip_access
46 from rhodecode.lib.auth import AuthUser
47 47 from rhodecode.lib.base import _get_ip_addr, _get_access_path
48 48 from rhodecode.lib.utils2 import safe_unicode
49 49
50 50 log = logging.getLogger('JSONRPC')
51 51
52 52
53 53 class JSONRPCError(BaseException):
54 54
55 55 def __init__(self, message):
56 56 self.message = message
57 57 super(JSONRPCError, self).__init__()
58 58
59 59 def __str__(self):
60 60 return str(self.message)
61 61
62 62
63 63 def jsonrpc_error(message, retid=None, code=None):
64 64 """
65 65 Generate a Response object with a JSON-RPC error body
66 66 """
67 67 from pylons.controllers.util import Response
68 68 return Response(
69 69 body=json.dumps(dict(id=retid, result=None, error=message)),
70 70 status=code,
71 71 content_type='application/json'
72 72 )
73 73
74 74
75 75 class JSONRPCController(WSGIController):
76 76 """
77 77 A WSGI-speaking JSON-RPC controller class
78 78
79 79 See the specification:
80 80 <http://json-rpc.org/wiki/specification>`.
81 81
82 82 Valid controller return values should be json-serializable objects.
83 83
84 84 Sub-classes should catch their exceptions and raise JSONRPCError
85 85 if they want to pass meaningful errors to the client.
86 86
87 87 """
88 88
89 89 def _get_ip_addr(self, environ):
90 90 return _get_ip_addr(environ)
91 91
92 92 def _get_method_args(self):
93 93 """
94 94 Return `self._rpc_args` to dispatched controller method
95 95 chosen by __call__
96 96 """
97 97 return self._rpc_args
98 98
99 99 def __call__(self, environ, start_response):
100 100 """
101 101 Parse the request body as JSON, look up the method on the
102 102 controller and if it exists, dispatch to it.
103 103 """
104 104 start = time.time()
105 105 ip_addr = self.ip_addr = self._get_ip_addr(environ)
106 106 self._req_id = None
107 107 if 'CONTENT_LENGTH' not in environ:
108 108 log.debug("No Content-Length")
109 109 return jsonrpc_error(retid=self._req_id,
110 110 message="No Content-Length in request")
111 111 else:
112 112 length = environ['CONTENT_LENGTH'] or 0
113 113 length = int(environ['CONTENT_LENGTH'])
114 114 log.debug('Content-Length: %s' % length)
115 115
116 116 if length == 0:
117 117 log.debug("Content-Length is 0")
118 118 return jsonrpc_error(retid=self._req_id,
119 119 message="Content-Length is 0")
120 120
121 121 raw_body = environ['wsgi.input'].read(length)
122 122
123 123 try:
124 124 json_body = json.loads(urllib.unquote_plus(raw_body))
125 125 except ValueError, e:
126 126 # catch JSON errors Here
127 127 return jsonrpc_error(retid=self._req_id,
128 128 message="JSON parse error ERR:%s RAW:%r" \
129 129 % (e, urllib.unquote_plus(raw_body)))
130 130
131 131 # check AUTH based on API KEY
132 132 try:
133 133 self._req_api_key = json_body['api_key']
134 134 self._req_id = json_body['id']
135 135 self._req_method = json_body['method']
136 136 self._request_params = json_body['args']
137 137 log.debug(
138 138 'method: %s, params: %s' % (self._req_method,
139 139 self._request_params)
140 140 )
141 141 except KeyError, e:
142 142 return jsonrpc_error(retid=self._req_id,
143 143 message='Incorrect JSON query missing %s' % e)
144 144
145 145 # check if we can find this session using api_key
146 146 try:
147 147 u = User.get_by_api_key(self._req_api_key)
148 148 if u is None:
149 149 return jsonrpc_error(retid=self._req_id,
150 150 message='Invalid API KEY')
151
151 152 #check if we are allowed to use this IP
152 allowed_ips = AuthUser.get_allowed_ips(u.user_id)
153 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips) is False:
154 log.info('Access for IP:%s forbidden, '
155 'not in %s' % (ip_addr, allowed_ips))
153 auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
154 if not auth_u.ip_allowed:
156 155 return jsonrpc_error(retid=self._req_id,
157 156 message='request from IP:%s not allowed' % (ip_addr))
158 157 else:
159 158 log.info('Access for IP:%s allowed' % (ip_addr))
160 159
161 auth_u = AuthUser(u.user_id, self._req_api_key, ip_addr=ip_addr)
162 160 except Exception, e:
163 161 return jsonrpc_error(retid=self._req_id,
164 162 message='Invalid API KEY')
165 163
166 164 self._error = None
167 165 try:
168 166 self._func = self._find_method()
169 167 except AttributeError, e:
170 168 return jsonrpc_error(retid=self._req_id,
171 169 message=str(e))
172 170
173 171 # now that we have a method, add self._req_params to
174 172 # self.kargs and dispatch control to WGIController
175 173 argspec = inspect.getargspec(self._func)
176 174 arglist = argspec[0][1:]
177 175 defaults = map(type, argspec[3] or [])
178 176 default_empty = types.NotImplementedType
179 177
180 178 # kw arguments required by this method
181 179 func_kwargs = dict(izip_longest(reversed(arglist), reversed(defaults),
182 180 fillvalue=default_empty))
183 181
184 182 # this is little trick to inject logged in user for
185 183 # perms decorators to work they expect the controller class to have
186 184 # rhodecode_user attribute set
187 185 self.rhodecode_user = auth_u
188 186
189 187 # This attribute will need to be first param of a method that uses
190 188 # api_key, which is translated to instance of user at that name
191 189 USER_SESSION_ATTR = 'apiuser'
192 190
193 191 if USER_SESSION_ATTR not in arglist:
194 192 return jsonrpc_error(
195 193 retid=self._req_id,
196 194 message='This method [%s] does not support '
197 195 'authentication (missing %s param)' % (
198 196 self._func.__name__, USER_SESSION_ATTR)
199 197 )
200 198
201 199 # get our arglist and check if we provided them as args
202 200 for arg, default in func_kwargs.iteritems():
203 201 if arg == USER_SESSION_ATTR:
204 202 # USER_SESSION_ATTR is something translated from api key and
205 203 # this is checked before so we don't need validate it
206 204 continue
207 205
208 206 # skip the required param check if it's default value is
209 207 # NotImplementedType (default_empty)
210 208 if (default == default_empty and arg not in self._request_params):
211 209 return jsonrpc_error(
212 210 retid=self._req_id,
213 211 message=(
214 212 'Missing non optional `%s` arg in JSON DATA' % arg
215 213 )
216 214 )
217 215
218 216 self._rpc_args = {USER_SESSION_ATTR: u}
219 217 self._rpc_args.update(self._request_params)
220 218
221 219 self._rpc_args['action'] = self._req_method
222 220 self._rpc_args['environ'] = environ
223 221 self._rpc_args['start_response'] = start_response
224 222
225 223 status = []
226 224 headers = []
227 225 exc_info = []
228 226
229 227 def change_content(new_status, new_headers, new_exc_info=None):
230 228 status.append(new_status)
231 229 headers.extend(new_headers)
232 230 exc_info.append(new_exc_info)
233 231
234 232 output = WSGIController.__call__(self, environ, change_content)
235 233 output = list(output)
236 234 headers.append(('Content-Length', str(len(output[0]))))
237 235 replace_header(headers, 'Content-Type', 'application/json')
238 236 start_response(status[0], headers, exc_info[0])
239 237 log.info('IP: %s Request to %s time: %.3fs' % (
240 238 _get_ip_addr(environ),
241 239 safe_unicode(_get_access_path(environ)), time.time() - start)
242 240 )
243 241 return output
244 242
245 243 def _dispatch_call(self):
246 244 """
247 245 Implement dispatch interface specified by WSGIController
248 246 """
249 247 try:
250 248 raw_response = self._inspect_call(self._func)
251 249 if isinstance(raw_response, HTTPError):
252 250 self._error = str(raw_response)
253 251 except JSONRPCError, e:
254 252 self._error = str(e)
255 253 except Exception, e:
256 254 log.error('Encountered unhandled exception: %s' \
257 255 % traceback.format_exc())
258 256 json_exc = JSONRPCError('Internal server error')
259 257 self._error = str(json_exc)
260 258
261 259 if self._error is not None:
262 260 raw_response = None
263 261
264 262 response = dict(id=self._req_id, result=raw_response,
265 263 error=self._error)
266 264
267 265 try:
268 266 return json.dumps(response)
269 267 except TypeError, e:
270 268 log.error('API FAILED. Error encoding response: %s' % e)
271 269 return json.dumps(
272 270 dict(
273 271 id=self._req_id,
274 272 result=None,
275 273 error="Error encoding response"
276 274 )
277 275 )
278 276
279 277 def _find_method(self):
280 278 """
281 279 Return method named by `self._req_method` in controller if able
282 280 """
283 281 log.debug('Trying to find JSON-RPC method: %s' % self._req_method)
284 282 if self._req_method.startswith('_'):
285 283 raise AttributeError("Method not allowed")
286 284
287 285 try:
288 286 func = getattr(self, self._req_method, None)
289 287 except UnicodeEncodeError:
290 288 raise AttributeError("Problem decoding unicode in requested "
291 289 "method name.")
292 290
293 291 if isinstance(func, types.MethodType):
294 292 return func
295 293 else:
296 294 raise AttributeError("No such method: %s" % self._req_method)
@@ -1,196 +1,195 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.login
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Login controller for rhodeocode
7 7
8 8 :created_on: Apr 22, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import formencode
28 28 import datetime
29 29 import urlparse
30 30
31 31 from formencode import htmlfill
32 32 from webob.exc import HTTPFound
33 33 from pylons.i18n.translation import _
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons import request, response, session, tmpl_context as c, url
36 36
37 37 import rhodecode.lib.helpers as h
38 38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.model.db import User
41 41 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.meta import Session
44 44
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48
49 49 class LoginController(BaseController):
50 50
51 51 def __before__(self):
52 52 super(LoginController, self).__before__()
53 53
54 54 def index(self):
55 55 # redirect if already logged in
56 56 c.came_from = request.GET.get('came_from')
57
58 if self.rhodecode_user.is_authenticated \
59 and self.rhodecode_user.username != 'default':
60
57 not_default = self.rhodecode_user.username != 'default'
58 ip_allowed = self.rhodecode_user.ip_allowed
59 if self.rhodecode_user.is_authenticated and not_default and ip_allowed:
61 60 return redirect(url('home'))
62 61
63 62 if request.POST:
64 63 # import Login Form validator class
65 64 login_form = LoginForm()
66 65 try:
67 66 session.invalidate()
68 67 c.form_result = login_form.to_python(dict(request.POST))
69 68 # form checks for username/password, now we're authenticated
70 69 username = c.form_result['username']
71 70 user = User.get_by_username(username, case_insensitive=True)
72 71 auth_user = AuthUser(user.user_id)
73 72 auth_user.set_authenticated()
74 73 cs = auth_user.get_cookie_store()
75 74 session['rhodecode_user'] = cs
76 75 user.update_lastlogin()
77 76 Session().commit()
78 77
79 78 # If they want to be remembered, update the cookie
80 79 if c.form_result['remember'] is not False:
81 80 _year = (datetime.datetime.now() +
82 81 datetime.timedelta(seconds=60 * 60 * 24 * 365))
83 82 session._set_cookie_expires(_year)
84 83
85 84 session.save()
86 85
87 86 log.info('user %s is now authenticated and stored in '
88 87 'session, session attrs %s' % (username, cs))
89 88
90 89 # dumps session attrs back to cookie
91 90 session._update_cookie_out()
92 91
93 92 # we set new cookie
94 93 headers = None
95 94 if session.request['set_cookie']:
96 95 # send set-cookie headers back to response to update cookie
97 96 headers = [('Set-Cookie', session.request['cookie_out'])]
98 97
99 98 allowed_schemes = ['http', 'https']
100 99 if c.came_from:
101 100 parsed = urlparse.urlparse(c.came_from)
102 101 server_parsed = urlparse.urlparse(url.current())
103 102 if parsed.scheme and parsed.scheme not in allowed_schemes:
104 103 log.error(
105 104 'Suspicious URL scheme detected %s for url %s' %
106 105 (parsed.scheme, parsed))
107 106 c.came_from = url('home')
108 107 elif server_parsed.netloc != parsed.netloc:
109 108 log.error('Suspicious NETLOC detected %s for url %s'
110 109 'server url is: %s' %
111 110 (parsed.netloc, parsed, server_parsed))
112 111 c.came_from = url('home')
113 112 raise HTTPFound(location=c.came_from, headers=headers)
114 113 else:
115 114 raise HTTPFound(location=url('home'), headers=headers)
116 115
117 116 except formencode.Invalid, errors:
118 117 return htmlfill.render(
119 118 render('/login.html'),
120 119 defaults=errors.value,
121 120 errors=errors.error_dict or {},
122 121 prefix_error=False,
123 122 encoding="UTF-8")
124 123
125 124 return render('/login.html')
126 125
127 126 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
128 127 'hg.register.manual_activate')
129 128 def register(self):
130 129 c.auto_active = False
131 130 for perm in User.get_by_username('default').user_perms:
132 131 if perm.permission.permission_name == 'hg.register.auto_activate':
133 132 c.auto_active = True
134 133 break
135 134
136 135 if request.POST:
137 136
138 137 register_form = RegisterForm()()
139 138 try:
140 139 form_result = register_form.to_python(dict(request.POST))
141 140 form_result['active'] = c.auto_active
142 141 UserModel().create_registration(form_result)
143 142 h.flash(_('You have successfully registered into rhodecode'),
144 143 category='success')
145 144 Session().commit()
146 145 return redirect(url('login_home'))
147 146
148 147 except formencode.Invalid, errors:
149 148 return htmlfill.render(
150 149 render('/register.html'),
151 150 defaults=errors.value,
152 151 errors=errors.error_dict or {},
153 152 prefix_error=False,
154 153 encoding="UTF-8")
155 154
156 155 return render('/register.html')
157 156
158 157 def password_reset(self):
159 158 if request.POST:
160 159 password_reset_form = PasswordResetForm()()
161 160 try:
162 161 form_result = password_reset_form.to_python(dict(request.POST))
163 162 UserModel().reset_password_link(form_result)
164 163 h.flash(_('Your password reset link was sent'),
165 164 category='success')
166 165 return redirect(url('login_home'))
167 166
168 167 except formencode.Invalid, errors:
169 168 return htmlfill.render(
170 169 render('/password_reset.html'),
171 170 defaults=errors.value,
172 171 errors=errors.error_dict or {},
173 172 prefix_error=False,
174 173 encoding="UTF-8")
175 174
176 175 return render('/password_reset.html')
177 176
178 177 def password_reset_confirmation(self):
179 178 if request.GET and request.GET.get('key'):
180 179 try:
181 180 user = User.get_by_api_key(request.GET.get('key'))
182 181 data = dict(email=user.email)
183 182 UserModel().reset_password(data)
184 183 h.flash(_('Your password reset was successful, '
185 184 'new password has been sent to your email'),
186 185 category='success')
187 186 except Exception, e:
188 187 log.error(e)
189 188 return redirect(url('reset_password'))
190 189
191 190 return redirect(url('login_home'))
192 191
193 192 def logout(self):
194 193 session.delete()
195 194 log.info('Logging out and deleting session for user')
196 195 redirect(url('home'))
@@ -1,851 +1,879 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 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import random
27 27 import logging
28 28 import traceback
29 29 import hashlib
30 30
31 31 from tempfile import _RandomNameSequence
32 32 from decorator import decorator
33 33
34 34 from pylons import config, url, request
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode import __platform__, is_windows, is_unix
39 39 from rhodecode.model.meta import Session
40 40
41 41 from rhodecode.lib.utils2 import str2bool, safe_unicode
42 42 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
43 43 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
44 44 from rhodecode.lib.auth_ldap import AuthLdap
45 45
46 46 from rhodecode.model import meta
47 47 from rhodecode.model.user import UserModel
48 48 from rhodecode.model.db import Permission, RhodeCodeSetting, User, UserIpMap
49 from rhodecode.lib.caching_query import FromCache
49 50
50 51 log = logging.getLogger(__name__)
51 52
52 53
53 54 class PasswordGenerator(object):
54 55 """
55 56 This is a simple class for generating password from different sets of
56 57 characters
57 58 usage::
58 59
59 60 passwd_gen = PasswordGenerator()
60 61 #print 8-letter password containing only big and small letters
61 62 of alphabet
62 63 passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 64 """
64 65 ALPHABETS_NUM = r'''1234567890'''
65 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
66 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
67 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
68 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
69 70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
70 71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
71 72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
73 74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
74 75
75 76 def __init__(self, passwd=''):
76 77 self.passwd = passwd
77 78
78 79 def gen_password(self, length, type_=None):
79 80 if type_ is None:
80 81 type_ = self.ALPHABETS_FULL
81 82 self.passwd = ''.join([random.choice(type_) for _ in xrange(length)])
82 83 return self.passwd
83 84
84 85
85 86 class RhodeCodeCrypto(object):
86 87
87 88 @classmethod
88 89 def hash_string(cls, str_):
89 90 """
90 91 Cryptographic function used for password hashing based on pybcrypt
91 92 or pycrypto in windows
92 93
93 94 :param password: password to hash
94 95 """
95 96 if is_windows:
96 97 from hashlib import sha256
97 98 return sha256(str_).hexdigest()
98 99 elif is_unix:
99 100 import bcrypt
100 101 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
101 102 else:
102 103 raise Exception('Unknown or unsupported platform %s' \
103 104 % __platform__)
104 105
105 106 @classmethod
106 107 def hash_check(cls, password, hashed):
107 108 """
108 109 Checks matching password with it's hashed value, runs different
109 110 implementation based on platform it runs on
110 111
111 112 :param password: password
112 113 :param hashed: password in hashed form
113 114 """
114 115
115 116 if is_windows:
116 117 from hashlib import sha256
117 118 return sha256(password).hexdigest() == hashed
118 119 elif is_unix:
119 120 import bcrypt
120 121 return bcrypt.hashpw(password, hashed) == hashed
121 122 else:
122 123 raise Exception('Unknown or unsupported platform %s' \
123 124 % __platform__)
124 125
125 126
126 127 def get_crypt_password(password):
127 128 return RhodeCodeCrypto.hash_string(password)
128 129
129 130
130 131 def check_password(password, hashed):
131 132 return RhodeCodeCrypto.hash_check(password, hashed)
132 133
133 134
134 135 def generate_api_key(str_, salt=None):
135 136 """
136 137 Generates API KEY from given string
137 138
138 139 :param str_:
139 140 :param salt:
140 141 """
141 142
142 143 if salt is None:
143 144 salt = _RandomNameSequence().next()
144 145
145 146 return hashlib.sha1(str_ + salt).hexdigest()
146 147
147 148
148 149 def authfunc(environ, username, password):
149 150 """
150 151 Dummy authentication wrapper function used in Mercurial and Git for
151 152 access control.
152 153
153 154 :param environ: needed only for using in Basic auth
154 155 """
155 156 return authenticate(username, password)
156 157
157 158
158 159 def authenticate(username, password):
159 160 """
160 161 Authentication function used for access control,
161 162 firstly checks for db authentication then if ldap is enabled for ldap
162 163 authentication, also creates ldap user if not in database
163 164
164 165 :param username: username
165 166 :param password: password
166 167 """
167 168
168 169 user_model = UserModel()
169 170 user = User.get_by_username(username)
170 171
171 172 log.debug('Authenticating user using RhodeCode account')
172 173 if user is not None and not user.ldap_dn:
173 174 if user.active:
174 175 if user.username == 'default' and user.active:
175 176 log.info('user %s authenticated correctly as anonymous user' %
176 177 username)
177 178 return True
178 179
179 180 elif user.username == username and check_password(password,
180 181 user.password):
181 182 log.info('user %s authenticated correctly' % username)
182 183 return True
183 184 else:
184 185 log.warning('user %s tried auth but is disabled' % username)
185 186
186 187 else:
187 188 log.debug('Regular authentication failed')
188 189 user_obj = User.get_by_username(username, case_insensitive=True)
189 190
190 191 if user_obj is not None and not user_obj.ldap_dn:
191 192 log.debug('this user already exists as non ldap')
192 193 return False
193 194
194 195 ldap_settings = RhodeCodeSetting.get_ldap_settings()
195 196 #======================================================================
196 197 # FALLBACK TO LDAP AUTH IF ENABLE
197 198 #======================================================================
198 199 if str2bool(ldap_settings.get('ldap_active')):
199 200 log.debug("Authenticating user using ldap")
200 201 kwargs = {
201 202 'server': ldap_settings.get('ldap_host', ''),
202 203 'base_dn': ldap_settings.get('ldap_base_dn', ''),
203 204 'port': ldap_settings.get('ldap_port'),
204 205 'bind_dn': ldap_settings.get('ldap_dn_user'),
205 206 'bind_pass': ldap_settings.get('ldap_dn_pass'),
206 207 'tls_kind': ldap_settings.get('ldap_tls_kind'),
207 208 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
208 209 'ldap_filter': ldap_settings.get('ldap_filter'),
209 210 'search_scope': ldap_settings.get('ldap_search_scope'),
210 211 'attr_login': ldap_settings.get('ldap_attr_login'),
211 212 'ldap_version': 3,
212 213 }
213 214 log.debug('Checking for ldap authentication')
214 215 try:
215 216 aldap = AuthLdap(**kwargs)
216 217 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
217 218 password)
218 219 log.debug('Got ldap DN response %s' % user_dn)
219 220
220 221 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
221 222 .get(k), [''])[0]
222 223
223 224 user_attrs = {
224 225 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
225 226 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
226 227 'email': get_ldap_attr('ldap_attr_email'),
227 228 }
228 229
229 230 # don't store LDAP password since we don't need it. Override
230 231 # with some random generated password
231 232 _password = PasswordGenerator().gen_password(length=8)
232 233 # create this user on the fly if it doesn't exist in rhodecode
233 234 # database
234 235 if user_model.create_ldap(username, _password, user_dn,
235 236 user_attrs):
236 237 log.info('created new ldap user %s' % username)
237 238
238 239 Session().commit()
239 240 return True
240 241 except (LdapUsernameError, LdapPasswordError,):
241 242 pass
242 243 except (Exception,):
243 244 log.error(traceback.format_exc())
244 245 pass
245 246 return False
246 247
247 248
248 249 def login_container_auth(username):
249 250 user = User.get_by_username(username)
250 251 if user is None:
251 252 user_attrs = {
252 253 'name': username,
253 254 'lastname': None,
254 255 'email': None,
255 256 }
256 257 user = UserModel().create_for_container_auth(username, user_attrs)
257 258 if not user:
258 259 return None
259 260 log.info('User %s was created by container authentication' % username)
260 261
261 262 if not user.active:
262 263 return None
263 264
264 265 user.update_lastlogin()
265 266 Session().commit()
266 267
267 268 log.debug('User %s is now logged in by container authentication',
268 269 user.username)
269 270 return user
270 271
271 272
272 273 def get_container_username(environ, config):
273 274 username = None
274 275
275 276 if str2bool(config.get('container_auth_enabled', False)):
276 277 from paste.httpheaders import REMOTE_USER
277 278 username = REMOTE_USER(environ)
278 279
279 280 if not username and str2bool(config.get('proxypass_auth_enabled', False)):
280 281 username = environ.get('HTTP_X_FORWARDED_USER')
281 282
282 283 if username:
283 284 # Removing realm and domain from username
284 285 username = username.partition('@')[0]
285 286 username = username.rpartition('\\')[2]
286 287 log.debug('Received username %s from container' % username)
287 288
288 289 return username
289 290
290 291
291 292 class CookieStoreWrapper(object):
292 293
293 294 def __init__(self, cookie_store):
294 295 self.cookie_store = cookie_store
295 296
296 297 def __repr__(self):
297 298 return 'CookieStore<%s>' % (self.cookie_store)
298 299
299 300 def get(self, key, other=None):
300 301 if isinstance(self.cookie_store, dict):
301 302 return self.cookie_store.get(key, other)
302 303 elif isinstance(self.cookie_store, AuthUser):
303 304 return self.cookie_store.__dict__.get(key, other)
304 305
305 306
306 307 class AuthUser(object):
307 308 """
308 309 A simple object that handles all attributes of user in RhodeCode
309 310
310 311 It does lookup based on API key,given user, or user present in session
311 312 Then it fills all required information for such user. It also checks if
312 313 anonymous access is enabled and if so, it returns default user as logged
313 314 in
314 315 """
315 316
316 317 def __init__(self, user_id=None, api_key=None, username=None, ip_addr=None):
317 318
318 319 self.user_id = user_id
319 320 self.api_key = None
320 321 self.username = username
321 322 self.ip_addr = ip_addr
322 323
323 324 self.name = ''
324 325 self.lastname = ''
325 326 self.email = ''
326 327 self.is_authenticated = False
327 328 self.admin = False
328 329 self.inherit_default_permissions = False
329 330 self.permissions = {}
330 self.allowed_ips = set()
331 331 self._api_key = api_key
332 332 self.propagate_data()
333 333 self._instance = None
334 334
335 335 def propagate_data(self):
336 336 user_model = UserModel()
337 337 self.anonymous_user = User.get_by_username('default', cache=True)
338 338 is_user_loaded = False
339 339
340 340 # try go get user by api key
341 341 if self._api_key and self._api_key != self.anonymous_user.api_key:
342 342 log.debug('Auth User lookup by API KEY %s' % self._api_key)
343 343 is_user_loaded = user_model.fill_data(self, api_key=self._api_key)
344 344 # lookup by userid
345 345 elif (self.user_id is not None and
346 346 self.user_id != self.anonymous_user.user_id):
347 347 log.debug('Auth User lookup by USER ID %s' % self.user_id)
348 348 is_user_loaded = user_model.fill_data(self, user_id=self.user_id)
349 349 # lookup by username
350 350 elif self.username and \
351 351 str2bool(config.get('container_auth_enabled', False)):
352 352
353 353 log.debug('Auth User lookup by USER NAME %s' % self.username)
354 354 dbuser = login_container_auth(self.username)
355 355 if dbuser is not None:
356 356 log.debug('filling all attributes to object')
357 357 for k, v in dbuser.get_dict().items():
358 358 setattr(self, k, v)
359 359 self.set_authenticated()
360 360 is_user_loaded = True
361 361 else:
362 362 log.debug('No data in %s that could been used to log in' % self)
363 363
364 364 if not is_user_loaded:
365 365 # if we cannot authenticate user try anonymous
366 366 if self.anonymous_user.active is True:
367 367 user_model.fill_data(self, user_id=self.anonymous_user.user_id)
368 368 # then we set this user is logged in
369 369 self.is_authenticated = True
370 370 else:
371 371 self.user_id = None
372 372 self.username = None
373 373 self.is_authenticated = False
374 374
375 375 if not self.username:
376 376 self.username = 'None'
377 377
378 378 log.debug('Auth User is now %s' % self)
379 379 user_model.fill_perms(self)
380 log.debug('Filling Allowed IPs')
381 self.allowed_ips = AuthUser.get_allowed_ips(self.user_id)
382 380
383 381 @property
384 382 def is_admin(self):
385 383 return self.admin
386 384
385 @property
386 def ip_allowed(self):
387 """
388 Checks if ip_addr used in constructor is allowed from defined list of
389 allowed ip_addresses for user
390
391 :returns: boolean, True if ip is in allowed ip range
392 """
393 #check IP
394 allowed_ips = AuthUser.get_allowed_ips(self.user_id, cache=True)
395 if check_ip_access(source_ip=self.ip_addr, allowed_ips=allowed_ips):
396 log.debug('IP:%s is in range of %s' % (self.ip_addr, allowed_ips))
397 return True
398 else:
399 log.info('Access for IP:%s forbidden, '
400 'not in %s' % (self.ip_addr, allowed_ips))
401 return False
402
387 403 def __repr__(self):
388 404 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
389 405 self.is_authenticated)
390 406
391 407 def set_authenticated(self, authenticated=True):
392 408 if self.user_id != self.anonymous_user.user_id:
393 409 self.is_authenticated = authenticated
394 410
395 411 def get_cookie_store(self):
396 412 return {'username': self.username,
397 413 'user_id': self.user_id,
398 414 'is_authenticated': self.is_authenticated}
399 415
400 416 @classmethod
401 417 def from_cookie_store(cls, cookie_store):
402 418 """
403 419 Creates AuthUser from a cookie store
404 420
405 421 :param cls:
406 422 :param cookie_store:
407 423 """
408 424 user_id = cookie_store.get('user_id')
409 425 username = cookie_store.get('username')
410 426 api_key = cookie_store.get('api_key')
411 427 return AuthUser(user_id, api_key, username)
412 428
413 429 @classmethod
414 def get_allowed_ips(cls, user_id):
430 def get_allowed_ips(cls, user_id, cache=False):
415 431 _set = set()
416 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id).all()
432 user_ips = UserIpMap.query().filter(UserIpMap.user_id == user_id)
433 if cache:
434 user_ips = user_ips.options(FromCache("sql_cache_short",
435 "get_user_ips_%s" % user_id))
417 436 for ip in user_ips:
418 437 _set.add(ip.ip_addr)
419 438 return _set or set(['0.0.0.0/0'])
420 439
421 440
422 441 def set_available_permissions(config):
423 442 """
424 443 This function will propagate pylons globals with all available defined
425 444 permission given in db. We don't want to check each time from db for new
426 445 permissions since adding a new permission also requires application restart
427 446 ie. to decorate new views with the newly created permission
428 447
429 448 :param config: current pylons config instance
430 449
431 450 """
432 451 log.info('getting information about all available permissions')
433 452 try:
434 453 sa = meta.Session
435 454 all_perms = sa.query(Permission).all()
436 455 except Exception:
437 456 pass
438 457 finally:
439 458 meta.Session.remove()
440 459
441 460 config['available_permissions'] = [x.permission_name for x in all_perms]
442 461
443 462
444 463 #==============================================================================
445 464 # CHECK DECORATORS
446 465 #==============================================================================
447 466 class LoginRequired(object):
448 467 """
449 468 Must be logged in to execute this function else
450 469 redirect to login page
451 470
452 471 :param api_access: if enabled this checks only for valid auth token
453 472 and grants access based on valid token
454 473 """
455 474
456 475 def __init__(self, api_access=False):
457 476 self.api_access = api_access
458 477
459 478 def __call__(self, func):
460 479 return decorator(self.__wrapper, func)
461 480
462 481 def __wrapper(self, func, *fargs, **fkwargs):
463 482 cls = fargs[0]
464 483 user = cls.rhodecode_user
484 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
485
486 #check IP
487 ip_access_ok = True
488 if not user.ip_allowed:
489 from rhodecode.lib import helpers as h
490 h.flash(h.literal(_('IP %s not allowed' % (user.ip_addr))),
491 category='warning')
492 ip_access_ok = False
465 493
466 494 api_access_ok = False
467 495 if self.api_access:
468 496 log.debug('Checking API KEY access for %s' % cls)
469 497 if user.api_key == request.GET.get('api_key'):
470 498 api_access_ok = True
471 499 else:
472 500 log.debug("API KEY token not valid")
473 loc = "%s:%s" % (cls.__class__.__name__, func.__name__)
501
474 502 log.debug('Checking if %s is authenticated @ %s' % (user.username, loc))
475 if user.is_authenticated or api_access_ok:
503 if (user.is_authenticated or api_access_ok) and ip_access_ok:
476 504 reason = 'RegularAuth' if user.is_authenticated else 'APIAuth'
477 505 log.info('user %s is authenticated and granted access to %s '
478 506 'using %s' % (user.username, loc, reason)
479 507 )
480 508 return func(*fargs, **fkwargs)
481 509 else:
482 510 log.warn('user %s NOT authenticated on func: %s' % (
483 511 user, loc)
484 512 )
485 513 p = url.current()
486 514
487 515 log.debug('redirecting to login page with %s' % p)
488 516 return redirect(url('login_home', came_from=p))
489 517
490 518
491 519 class NotAnonymous(object):
492 520 """
493 521 Must be logged in to execute this function else
494 522 redirect to login page"""
495 523
496 524 def __call__(self, func):
497 525 return decorator(self.__wrapper, func)
498 526
499 527 def __wrapper(self, func, *fargs, **fkwargs):
500 528 cls = fargs[0]
501 529 self.user = cls.rhodecode_user
502 530
503 531 log.debug('Checking if user is not anonymous @%s' % cls)
504 532
505 533 anonymous = self.user.username == 'default'
506 534
507 535 if anonymous:
508 536 p = url.current()
509 537
510 538 import rhodecode.lib.helpers as h
511 539 h.flash(_('You need to be a registered user to '
512 540 'perform this action'),
513 541 category='warning')
514 542 return redirect(url('login_home', came_from=p))
515 543 else:
516 544 return func(*fargs, **fkwargs)
517 545
518 546
519 547 class PermsDecorator(object):
520 548 """Base class for controller decorators"""
521 549
522 550 def __init__(self, *required_perms):
523 551 available_perms = config['available_permissions']
524 552 for perm in required_perms:
525 553 if perm not in available_perms:
526 554 raise Exception("'%s' permission is not defined" % perm)
527 555 self.required_perms = set(required_perms)
528 556 self.user_perms = None
529 557
530 558 def __call__(self, func):
531 559 return decorator(self.__wrapper, func)
532 560
533 561 def __wrapper(self, func, *fargs, **fkwargs):
534 562 cls = fargs[0]
535 563 self.user = cls.rhodecode_user
536 564 self.user_perms = self.user.permissions
537 565 log.debug('checking %s permissions %s for %s %s',
538 566 self.__class__.__name__, self.required_perms, cls, self.user)
539 567
540 568 if self.check_permissions():
541 569 log.debug('Permission granted for %s %s' % (cls, self.user))
542 570 return func(*fargs, **fkwargs)
543 571
544 572 else:
545 573 log.debug('Permission denied for %s %s' % (cls, self.user))
546 574 anonymous = self.user.username == 'default'
547 575
548 576 if anonymous:
549 577 p = url.current()
550 578
551 579 import rhodecode.lib.helpers as h
552 580 h.flash(_('You need to be a signed in to '
553 581 'view this page'),
554 582 category='warning')
555 583 return redirect(url('login_home', came_from=p))
556 584
557 585 else:
558 586 # redirect with forbidden ret code
559 587 return abort(403)
560 588
561 589 def check_permissions(self):
562 590 """Dummy function for overriding"""
563 591 raise Exception('You have to write this function in child class')
564 592
565 593
566 594 class HasPermissionAllDecorator(PermsDecorator):
567 595 """
568 596 Checks for access permission for all given predicates. All of them
569 597 have to be meet in order to fulfill the request
570 598 """
571 599
572 600 def check_permissions(self):
573 601 if self.required_perms.issubset(self.user_perms.get('global')):
574 602 return True
575 603 return False
576 604
577 605
578 606 class HasPermissionAnyDecorator(PermsDecorator):
579 607 """
580 608 Checks for access permission for any of given predicates. In order to
581 609 fulfill the request any of predicates must be meet
582 610 """
583 611
584 612 def check_permissions(self):
585 613 if self.required_perms.intersection(self.user_perms.get('global')):
586 614 return True
587 615 return False
588 616
589 617
590 618 class HasRepoPermissionAllDecorator(PermsDecorator):
591 619 """
592 620 Checks for access permission for all given predicates for specific
593 621 repository. All of them have to be meet in order to fulfill the request
594 622 """
595 623
596 624 def check_permissions(self):
597 625 repo_name = get_repo_slug(request)
598 626 try:
599 627 user_perms = set([self.user_perms['repositories'][repo_name]])
600 628 except KeyError:
601 629 return False
602 630 if self.required_perms.issubset(user_perms):
603 631 return True
604 632 return False
605 633
606 634
607 635 class HasRepoPermissionAnyDecorator(PermsDecorator):
608 636 """
609 637 Checks for access permission for any of given predicates for specific
610 638 repository. In order to fulfill the request any of predicates must be meet
611 639 """
612 640
613 641 def check_permissions(self):
614 642 repo_name = get_repo_slug(request)
615 643
616 644 try:
617 645 user_perms = set([self.user_perms['repositories'][repo_name]])
618 646 except KeyError:
619 647 return False
620 648
621 649 if self.required_perms.intersection(user_perms):
622 650 return True
623 651 return False
624 652
625 653
626 654 class HasReposGroupPermissionAllDecorator(PermsDecorator):
627 655 """
628 656 Checks for access permission for all given predicates for specific
629 657 repository. All of them have to be meet in order to fulfill the request
630 658 """
631 659
632 660 def check_permissions(self):
633 661 group_name = get_repos_group_slug(request)
634 662 try:
635 663 user_perms = set([self.user_perms['repositories_groups'][group_name]])
636 664 except KeyError:
637 665 return False
638 666 if self.required_perms.issubset(user_perms):
639 667 return True
640 668 return False
641 669
642 670
643 671 class HasReposGroupPermissionAnyDecorator(PermsDecorator):
644 672 """
645 673 Checks for access permission for any of given predicates for specific
646 674 repository. In order to fulfill the request any of predicates must be meet
647 675 """
648 676
649 677 def check_permissions(self):
650 678 group_name = get_repos_group_slug(request)
651 679
652 680 try:
653 681 user_perms = set([self.user_perms['repositories_groups'][group_name]])
654 682 except KeyError:
655 683 return False
656 684 if self.required_perms.intersection(user_perms):
657 685 return True
658 686 return False
659 687
660 688
661 689 #==============================================================================
662 690 # CHECK FUNCTIONS
663 691 #==============================================================================
664 692 class PermsFunction(object):
665 693 """Base function for other check functions"""
666 694
667 695 def __init__(self, *perms):
668 696 available_perms = config['available_permissions']
669 697
670 698 for perm in perms:
671 699 if perm not in available_perms:
672 700 raise Exception("'%s' permission is not defined" % perm)
673 701 self.required_perms = set(perms)
674 702 self.user_perms = None
675 703 self.repo_name = None
676 704 self.group_name = None
677 705
678 706 def __call__(self, check_Location=''):
679 707 user = request.user
680 708 cls_name = self.__class__.__name__
681 709 check_scope = {
682 710 'HasPermissionAll': '',
683 711 'HasPermissionAny': '',
684 712 'HasRepoPermissionAll': 'repo:%s' % self.repo_name,
685 713 'HasRepoPermissionAny': 'repo:%s' % self.repo_name,
686 714 'HasReposGroupPermissionAll': 'group:%s' % self.group_name,
687 715 'HasReposGroupPermissionAny': 'group:%s' % self.group_name,
688 716 }.get(cls_name, '?')
689 717 log.debug('checking cls:%s %s usr:%s %s @ %s', cls_name,
690 718 self.required_perms, user, check_scope,
691 719 check_Location or 'unspecified location')
692 720 if not user:
693 721 log.debug('Empty request user')
694 722 return False
695 723 self.user_perms = user.permissions
696 724 if self.check_permissions():
697 725 log.debug('Permission to %s granted for user: %s @ %s', self.repo_name, user,
698 726 check_Location or 'unspecified location')
699 727 return True
700 728
701 729 else:
702 730 log.debug('Permission to %s denied for user: %s @ %s', self.repo_name, user,
703 731 check_Location or 'unspecified location')
704 732 return False
705 733
706 734 def check_permissions(self):
707 735 """Dummy function for overriding"""
708 736 raise Exception('You have to write this function in child class')
709 737
710 738
711 739 class HasPermissionAll(PermsFunction):
712 740 def check_permissions(self):
713 741 if self.required_perms.issubset(self.user_perms.get('global')):
714 742 return True
715 743 return False
716 744
717 745
718 746 class HasPermissionAny(PermsFunction):
719 747 def check_permissions(self):
720 748 if self.required_perms.intersection(self.user_perms.get('global')):
721 749 return True
722 750 return False
723 751
724 752
725 753 class HasRepoPermissionAll(PermsFunction):
726 754 def __call__(self, repo_name=None, check_Location=''):
727 755 self.repo_name = repo_name
728 756 return super(HasRepoPermissionAll, self).__call__(check_Location)
729 757
730 758 def check_permissions(self):
731 759 if not self.repo_name:
732 760 self.repo_name = get_repo_slug(request)
733 761
734 762 try:
735 763 self._user_perms = set(
736 764 [self.user_perms['repositories'][self.repo_name]]
737 765 )
738 766 except KeyError:
739 767 return False
740 768 if self.required_perms.issubset(self._user_perms):
741 769 return True
742 770 return False
743 771
744 772
745 773 class HasRepoPermissionAny(PermsFunction):
746 774 def __call__(self, repo_name=None, check_Location=''):
747 775 self.repo_name = repo_name
748 776 return super(HasRepoPermissionAny, self).__call__(check_Location)
749 777
750 778 def check_permissions(self):
751 779 if not self.repo_name:
752 780 self.repo_name = get_repo_slug(request)
753 781
754 782 try:
755 783 self._user_perms = set(
756 784 [self.user_perms['repositories'][self.repo_name]]
757 785 )
758 786 except KeyError:
759 787 return False
760 788 if self.required_perms.intersection(self._user_perms):
761 789 return True
762 790 return False
763 791
764 792
765 793 class HasReposGroupPermissionAny(PermsFunction):
766 794 def __call__(self, group_name=None, check_Location=''):
767 795 self.group_name = group_name
768 796 return super(HasReposGroupPermissionAny, self).__call__(check_Location)
769 797
770 798 def check_permissions(self):
771 799 try:
772 800 self._user_perms = set(
773 801 [self.user_perms['repositories_groups'][self.group_name]]
774 802 )
775 803 except KeyError:
776 804 return False
777 805 if self.required_perms.intersection(self._user_perms):
778 806 return True
779 807 return False
780 808
781 809
782 810 class HasReposGroupPermissionAll(PermsFunction):
783 811 def __call__(self, group_name=None, check_Location=''):
784 812 self.group_name = group_name
785 813 return super(HasReposGroupPermissionAll, self).__call__(check_Location)
786 814
787 815 def check_permissions(self):
788 816 try:
789 817 self._user_perms = set(
790 818 [self.user_perms['repositories_groups'][self.group_name]]
791 819 )
792 820 except KeyError:
793 821 return False
794 822 if self.required_perms.issubset(self._user_perms):
795 823 return True
796 824 return False
797 825
798 826
799 827 #==============================================================================
800 828 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
801 829 #==============================================================================
802 830 class HasPermissionAnyMiddleware(object):
803 831 def __init__(self, *perms):
804 832 self.required_perms = set(perms)
805 833
806 834 def __call__(self, user, repo_name):
807 835 # repo_name MUST be unicode, since we handle keys in permission
808 836 # dict by unicode
809 837 repo_name = safe_unicode(repo_name)
810 838 usr = AuthUser(user.user_id)
811 839 try:
812 840 self.user_perms = set([usr.permissions['repositories'][repo_name]])
813 841 except Exception:
814 842 log.error('Exception while accessing permissions %s' %
815 843 traceback.format_exc())
816 844 self.user_perms = set()
817 845 self.username = user.username
818 846 self.repo_name = repo_name
819 847 return self.check_permissions()
820 848
821 849 def check_permissions(self):
822 850 log.debug('checking VCS protocol '
823 851 'permissions %s for user:%s repository:%s', self.user_perms,
824 852 self.username, self.repo_name)
825 853 if self.required_perms.intersection(self.user_perms):
826 854 log.debug('permission granted for user:%s on repo:%s' % (
827 855 self.username, self.repo_name
828 856 )
829 857 )
830 858 return True
831 859 log.debug('permission denied for user:%s on repo:%s' % (
832 860 self.username, self.repo_name
833 861 )
834 862 )
835 863 return False
836 864
837 865
838 866 def check_ip_access(source_ip, allowed_ips=None):
839 867 """
840 868 Checks if source_ip is a subnet of any of allowed_ips.
841 869
842 870 :param source_ip:
843 871 :param allowed_ips: list of allowed ips together with mask
844 872 """
845 873 from rhodecode.lib import ipaddr
846 874 log.debug('checking if ip:%s is subnet of %s' % (source_ip, allowed_ips))
847 875 if isinstance(allowed_ips, (tuple, list, set)):
848 876 for ip in allowed_ips:
849 877 if ipaddr.IPAddress(source_ip) in ipaddr.IPNetwork(ip):
850 878 return True
851 879 return False
@@ -1,334 +1,332 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
21 21 safe_str, safe_int
22 22 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
23 HasPermissionAnyMiddleware, CookieStoreWrapper, check_ip_access
23 HasPermissionAnyMiddleware, CookieStoreWrapper
24 24 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
25 25 from rhodecode.model import meta
26 26
27 27 from rhodecode.model.db import Repository, RhodeCodeUi, User, RhodeCodeSetting
28 28 from rhodecode.model.notification import NotificationModel
29 29 from rhodecode.model.scm import ScmModel
30 30 from rhodecode.model.meta import Session
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 def _get_ip_addr(environ):
36 36 proxy_key = 'HTTP_X_REAL_IP'
37 37 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
38 38 def_key = 'REMOTE_ADDR'
39 39
40 40 ip = environ.get(proxy_key2)
41 41 if ip:
42 42 return ip
43 43
44 44 ip = environ.get(proxy_key)
45 45
46 46 if ip:
47 47 return ip
48 48
49 49 ip = environ.get(def_key, '0.0.0.0')
50 50 return ip
51 51
52 52
53 53 def _get_access_path(environ):
54 54 path = environ.get('PATH_INFO')
55 55 org_req = environ.get('pylons.original_request')
56 56 if org_req:
57 57 path = org_req.environ.get('PATH_INFO')
58 58 return path
59 59
60 60
61 61 class BasicAuth(AuthBasicAuthenticator):
62 62
63 63 def __init__(self, realm, authfunc, auth_http_code=None):
64 64 self.realm = realm
65 65 self.authfunc = authfunc
66 66 self._rc_auth_http_code = auth_http_code
67 67
68 68 def build_authentication(self):
69 69 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
70 70 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
71 71 # return 403 if alternative http return code is specified in
72 72 # RhodeCode config
73 73 return HTTPForbidden(headers=head)
74 74 return HTTPUnauthorized(headers=head)
75 75
76 76 def authenticate(self, environ):
77 77 authorization = AUTHORIZATION(environ)
78 78 if not authorization:
79 79 return self.build_authentication()
80 80 (authmeth, auth) = authorization.split(' ', 1)
81 81 if 'basic' != authmeth.lower():
82 82 return self.build_authentication()
83 83 auth = auth.strip().decode('base64')
84 84 _parts = auth.split(':', 1)
85 85 if len(_parts) == 2:
86 86 username, password = _parts
87 87 if self.authfunc(environ, username, password):
88 88 return username
89 89 return self.build_authentication()
90 90
91 91 __call__ = authenticate
92 92
93 93
94 94 class BaseVCSController(object):
95 95
96 96 def __init__(self, application, config):
97 97 self.application = application
98 98 self.config = config
99 99 # base path of repo locations
100 100 self.basepath = self.config['base_path']
101 101 #authenticate this mercurial request using authfunc
102 102 self.authenticate = BasicAuth('', authfunc,
103 103 config.get('auth_ret_code'))
104 104 self.ip_addr = '0.0.0.0'
105 105
106 106 def _handle_request(self, environ, start_response):
107 107 raise NotImplementedError()
108 108
109 109 def _get_by_id(self, repo_name):
110 110 """
111 111 Get's a special pattern _<ID> from clone url and tries to replace it
112 112 with a repository_name for support of _<ID> non changable urls
113 113
114 114 :param repo_name:
115 115 """
116 116 try:
117 117 data = repo_name.split('/')
118 118 if len(data) >= 2:
119 119 by_id = data[1].split('_')
120 120 if len(by_id) == 2 and by_id[1].isdigit():
121 121 _repo_name = Repository.get(by_id[1]).repo_name
122 122 data[1] = _repo_name
123 123 except:
124 124 log.debug('Failed to extract repo_name from id %s' % (
125 125 traceback.format_exc()
126 126 )
127 127 )
128 128
129 129 return '/'.join(data)
130 130
131 131 def _invalidate_cache(self, repo_name):
132 132 """
133 133 Set's cache for this repository for invalidation on next access
134 134
135 135 :param repo_name: full repo name, also a cache key
136 136 """
137 137 invalidate_cache('get_repo_cached_%s' % repo_name)
138 138
139 139 def _check_permission(self, action, user, repo_name, ip_addr=None):
140 140 """
141 141 Checks permissions using action (push/pull) user and repository
142 142 name
143 143
144 144 :param action: push or pull action
145 145 :param user: user instance
146 146 :param repo_name: repository name
147 147 """
148 148 #check IP
149 allowed_ips = AuthUser.get_allowed_ips(user.user_id)
150 if check_ip_access(source_ip=ip_addr, allowed_ips=allowed_ips) is False:
151 log.info('Access for IP:%s forbidden, '
152 'not in %s' % (ip_addr, allowed_ips))
149 authuser = AuthUser(user_id=user.user_id, ip_addr=ip_addr)
150 if not authuser.ip_allowed:
153 151 return False
154 152 else:
155 153 log.info('Access for IP:%s allowed' % (ip_addr))
156 154 if action == 'push':
157 155 if not HasPermissionAnyMiddleware('repository.write',
158 156 'repository.admin')(user,
159 157 repo_name):
160 158 return False
161 159
162 160 else:
163 161 #any other action need at least read permission
164 162 if not HasPermissionAnyMiddleware('repository.read',
165 163 'repository.write',
166 164 'repository.admin')(user,
167 165 repo_name):
168 166 return False
169 167
170 168 return True
171 169
172 170 def _get_ip_addr(self, environ):
173 171 return _get_ip_addr(environ)
174 172
175 173 def _check_ssl(self, environ, start_response):
176 174 """
177 175 Checks the SSL check flag and returns False if SSL is not present
178 176 and required True otherwise
179 177 """
180 178 org_proto = environ['wsgi._org_proto']
181 179 #check if we have SSL required ! if not it's a bad request !
182 180 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
183 181 if require_ssl and org_proto == 'http':
184 182 log.debug('proto is %s and SSL is required BAD REQUEST !'
185 183 % org_proto)
186 184 return False
187 185 return True
188 186
189 187 def _check_locking_state(self, environ, action, repo, user_id):
190 188 """
191 189 Checks locking on this repository, if locking is enabled and lock is
192 190 present returns a tuple of make_lock, locked, locked_by.
193 191 make_lock can have 3 states None (do nothing) True, make lock
194 192 False release lock, This value is later propagated to hooks, which
195 193 do the locking. Think about this as signals passed to hooks what to do.
196 194
197 195 """
198 196 locked = False # defines that locked error should be thrown to user
199 197 make_lock = None
200 198 repo = Repository.get_by_repo_name(repo)
201 199 user = User.get(user_id)
202 200
203 201 # this is kind of hacky, but due to how mercurial handles client-server
204 202 # server see all operation on changeset; bookmarks, phases and
205 203 # obsolescence marker in different transaction, we don't want to check
206 204 # locking on those
207 205 obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
208 206 locked_by = repo.locked
209 207 if repo and repo.enable_locking and not obsolete_call:
210 208 if action == 'push':
211 209 #check if it's already locked !, if it is compare users
212 210 user_id, _date = repo.locked
213 211 if user.user_id == user_id:
214 212 log.debug('Got push from user %s, now unlocking' % (user))
215 213 # unlock if we have push from user who locked
216 214 make_lock = False
217 215 else:
218 216 # we're not the same user who locked, ban with 423 !
219 217 locked = True
220 218 if action == 'pull':
221 219 if repo.locked[0] and repo.locked[1]:
222 220 locked = True
223 221 else:
224 222 log.debug('Setting lock on repo %s by %s' % (repo, user))
225 223 make_lock = True
226 224
227 225 else:
228 226 log.debug('Repository %s do not have locking enabled' % (repo))
229 227 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
230 228 % (make_lock, locked, locked_by))
231 229 return make_lock, locked, locked_by
232 230
233 231 def __call__(self, environ, start_response):
234 232 start = time.time()
235 233 try:
236 234 return self._handle_request(environ, start_response)
237 235 finally:
238 236 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
239 237 log.debug('Request time: %.3fs' % (time.time() - start))
240 238 meta.Session.remove()
241 239
242 240
243 241 class BaseController(WSGIController):
244 242
245 243 def __before__(self):
246 244 """
247 245 __before__ is called before controller methods and after __call__
248 246 """
249 247 c.rhodecode_version = __version__
250 248 c.rhodecode_instanceid = config.get('instance_id')
251 249 c.rhodecode_name = config.get('rhodecode_title')
252 250 c.use_gravatar = str2bool(config.get('use_gravatar'))
253 251 c.ga_code = config.get('rhodecode_ga_code')
254 252 # Visual options
255 253 c.visual = AttributeDict({})
256 254 rc_config = RhodeCodeSetting.get_app_settings()
257 255
258 256 c.visual.show_public_icon = str2bool(rc_config.get('rhodecode_show_public_icon'))
259 257 c.visual.show_private_icon = str2bool(rc_config.get('rhodecode_show_private_icon'))
260 258 c.visual.stylify_metatags = str2bool(rc_config.get('rhodecode_stylify_metatags'))
261 259 c.visual.lightweight_dashboard = str2bool(rc_config.get('rhodecode_lightweight_dashboard'))
262 260 c.visual.lightweight_dashboard_items = safe_int(config.get('dashboard_items', 100))
263 261
264 262 c.repo_name = get_repo_slug(request)
265 263 c.backends = BACKENDS.keys()
266 264 c.unread_notifications = NotificationModel()\
267 265 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
268 266 self.cut_off_limit = int(config.get('cut_off_limit'))
269 267
270 268 self.sa = meta.Session
271 269 self.scm_model = ScmModel(self.sa)
272 270
273 271 def __call__(self, environ, start_response):
274 272 """Invoke the Controller"""
275 273 # WSGIController.__call__ dispatches to the Controller method
276 274 # the request is routed to. This routing information is
277 275 # available in environ['pylons.routes_dict']
278 276 start = time.time()
279 277 try:
280 278 self.ip_addr = _get_ip_addr(environ)
281 279 # make sure that we update permissions each time we call controller
282 280 api_key = request.GET.get('api_key')
283 281 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
284 282 user_id = cookie_store.get('user_id', None)
285 283 username = get_container_username(environ, config)
286 284 auth_user = AuthUser(user_id, api_key, username, self.ip_addr)
287 285 request.user = auth_user
288 286 self.rhodecode_user = c.rhodecode_user = auth_user
289 287 if not self.rhodecode_user.is_authenticated and \
290 288 self.rhodecode_user.user_id is not None:
291 289 self.rhodecode_user.set_authenticated(
292 290 cookie_store.get('is_authenticated')
293 291 )
294 292 log.info('IP: %s User: %s accessed %s' % (
295 293 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
296 294 )
297 295 return WSGIController.__call__(self, environ, start_response)
298 296 finally:
299 297 log.info('IP: %s Request to %s time: %.3fs' % (
300 298 _get_ip_addr(environ),
301 299 safe_unicode(_get_access_path(environ)), time.time() - start)
302 300 )
303 301 meta.Session.remove()
304 302
305 303
306 304 class BaseRepoController(BaseController):
307 305 """
308 306 Base class for controllers responsible for loading all needed data for
309 307 repository loaded items are
310 308
311 309 c.rhodecode_repo: instance of scm repository
312 310 c.rhodecode_db_repo: instance of db
313 311 c.repository_followers: number of followers
314 312 c.repository_forks: number of forks
315 313 """
316 314
317 315 def __before__(self):
318 316 super(BaseRepoController, self).__before__()
319 317 if c.repo_name:
320 318
321 319 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
322 320 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
323 321 # update last change according to VCS data
324 322 dbr.update_last_change(c.rhodecode_repo.last_change)
325 323 if c.rhodecode_repo is None:
326 324 log.error('%s this repository is present in database but it '
327 325 'cannot be created as an scm instance', c.repo_name)
328 326
329 327 redirect(url('home'))
330 328
331 329 # some globals counter for menu
332 330 c.repository_followers = self.scm_model.get_followers(dbr)
333 331 c.repository_forks = self.scm_model.get_forks(dbr)
334 332 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,1871 +1,1872 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix, remove_prefix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 402 def __unicode__(self):
403 403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 404 self.user_id, self.username)
405 405
406 406 @classmethod
407 407 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 408 if case_insensitive:
409 409 q = cls.query().filter(cls.username.ilike(username))
410 410 else:
411 411 q = cls.query().filter(cls.username == username)
412 412
413 413 if cache:
414 414 q = q.options(FromCache(
415 415 "sql_cache_short",
416 416 "get_user_%s" % _hash_key(username)
417 417 )
418 418 )
419 419 return q.scalar()
420 420
421 421 @classmethod
422 422 def get_by_api_key(cls, api_key, cache=False):
423 423 q = cls.query().filter(cls.api_key == api_key)
424 424
425 425 if cache:
426 426 q = q.options(FromCache("sql_cache_short",
427 427 "get_api_key_%s" % api_key))
428 428 return q.scalar()
429 429
430 430 @classmethod
431 431 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 432 if case_insensitive:
433 433 q = cls.query().filter(cls.email.ilike(email))
434 434 else:
435 435 q = cls.query().filter(cls.email == email)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_email_key_%s" % email))
440 440
441 441 ret = q.scalar()
442 442 if ret is None:
443 443 q = UserEmailMap.query()
444 444 # try fetching in alternate email map
445 445 if case_insensitive:
446 446 q = q.filter(UserEmailMap.email.ilike(email))
447 447 else:
448 448 q = q.filter(UserEmailMap.email == email)
449 449 q = q.options(joinedload(UserEmailMap.user))
450 450 if cache:
451 451 q = q.options(FromCache("sql_cache_short",
452 452 "get_email_map_key_%s" % email))
453 453 ret = getattr(q.scalar(), 'user', None)
454 454
455 455 return ret
456 456
457 457 def update_lastlogin(self):
458 458 """Update user lastlogin"""
459 459 self.last_login = datetime.datetime.now()
460 460 Session().add(self)
461 461 log.debug('updated user %s lastlogin' % self.username)
462 462
463 463 def get_api_data(self):
464 464 """
465 465 Common function for generating user related data for API
466 466 """
467 467 user = self
468 468 data = dict(
469 469 user_id=user.user_id,
470 470 username=user.username,
471 471 firstname=user.name,
472 472 lastname=user.lastname,
473 473 email=user.email,
474 474 emails=user.emails,
475 475 api_key=user.api_key,
476 476 active=user.active,
477 477 admin=user.admin,
478 478 ldap_dn=user.ldap_dn,
479 479 last_login=user.last_login,
480 480 ip_addresses=user.ip_addresses
481 481 )
482 482 return data
483 483
484 484 def __json__(self):
485 485 data = dict(
486 486 full_name=self.full_name,
487 487 full_name_or_username=self.full_name_or_username,
488 488 short_contact=self.short_contact,
489 489 full_contact=self.full_contact
490 490 )
491 491 data.update(self.get_api_data())
492 492 return data
493 493
494 494
495 495 class UserEmailMap(Base, BaseModel):
496 496 __tablename__ = 'user_email_map'
497 497 __table_args__ = (
498 498 Index('uem_email_idx', 'email'),
499 499 UniqueConstraint('email'),
500 500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 501 'mysql_charset': 'utf8'}
502 502 )
503 503 __mapper_args__ = {}
504 504
505 505 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 506 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
507 507 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
508 508 user = relationship('User', lazy='joined')
509 509
510 510 @validates('_email')
511 511 def validate_email(self, key, email):
512 512 # check if this email is not main one
513 513 main_email = Session().query(User).filter(User.email == email).scalar()
514 514 if main_email is not None:
515 515 raise AttributeError('email %s is present is user table' % email)
516 516 return email
517 517
518 518 @hybrid_property
519 519 def email(self):
520 520 return self._email
521 521
522 522 @email.setter
523 523 def email(self, val):
524 524 self._email = val.lower() if val else None
525 525
526 526
527 527 class UserIpMap(Base, BaseModel):
528 528 __tablename__ = 'user_ip_map'
529 529 __table_args__ = (
530 530 UniqueConstraint('user_id', 'ip_addr'),
531 531 {'extend_existing': True, 'mysql_engine': 'InnoDB',
532 532 'mysql_charset': 'utf8'}
533 533 )
534 534 __mapper_args__ = {}
535 535
536 536 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
537 537 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
538 538 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
539 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
539 540 user = relationship('User', lazy='joined')
540 541
541 542 @classmethod
542 543 def _get_ip_range(cls, ip_addr):
543 544 from rhodecode.lib import ipaddr
544 545 net = ipaddr.IPv4Network(ip_addr)
545 546 return [str(net.network), str(net.broadcast)]
546 547
547 548 def __json__(self):
548 549 return dict(
549 550 ip_addr=self.ip_addr,
550 551 ip_range=self._get_ip_range(self.ip_addr)
551 552 )
552 553
553 554
554 555 class UserLog(Base, BaseModel):
555 556 __tablename__ = 'user_logs'
556 557 __table_args__ = (
557 558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
558 559 'mysql_charset': 'utf8'},
559 560 )
560 561 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
561 562 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
562 563 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
563 564 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
564 565 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
565 566 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
566 567 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
567 568 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
568 569
569 570 @property
570 571 def action_as_day(self):
571 572 return datetime.date(*self.action_date.timetuple()[:3])
572 573
573 574 user = relationship('User')
574 575 repository = relationship('Repository', cascade='')
575 576
576 577
577 578 class UsersGroup(Base, BaseModel):
578 579 __tablename__ = 'users_groups'
579 580 __table_args__ = (
580 581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
581 582 'mysql_charset': 'utf8'},
582 583 )
583 584
584 585 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
585 586 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
586 587 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
587 588 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
588 589
589 590 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
590 591 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
591 592 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
592 593
593 594 def __unicode__(self):
594 595 return u'<userGroup(%s)>' % (self.users_group_name)
595 596
596 597 @classmethod
597 598 def get_by_group_name(cls, group_name, cache=False,
598 599 case_insensitive=False):
599 600 if case_insensitive:
600 601 q = cls.query().filter(cls.users_group_name.ilike(group_name))
601 602 else:
602 603 q = cls.query().filter(cls.users_group_name == group_name)
603 604 if cache:
604 605 q = q.options(FromCache(
605 606 "sql_cache_short",
606 607 "get_user_%s" % _hash_key(group_name)
607 608 )
608 609 )
609 610 return q.scalar()
610 611
611 612 @classmethod
612 613 def get(cls, users_group_id, cache=False):
613 614 users_group = cls.query()
614 615 if cache:
615 616 users_group = users_group.options(FromCache("sql_cache_short",
616 617 "get_users_group_%s" % users_group_id))
617 618 return users_group.get(users_group_id)
618 619
619 620 def get_api_data(self):
620 621 users_group = self
621 622
622 623 data = dict(
623 624 users_group_id=users_group.users_group_id,
624 625 group_name=users_group.users_group_name,
625 626 active=users_group.users_group_active,
626 627 )
627 628
628 629 return data
629 630
630 631
631 632 class UsersGroupMember(Base, BaseModel):
632 633 __tablename__ = 'users_groups_members'
633 634 __table_args__ = (
634 635 {'extend_existing': True, 'mysql_engine': 'InnoDB',
635 636 'mysql_charset': 'utf8'},
636 637 )
637 638
638 639 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
639 640 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
640 641 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
641 642
642 643 user = relationship('User', lazy='joined')
643 644 users_group = relationship('UsersGroup')
644 645
645 646 def __init__(self, gr_id='', u_id=''):
646 647 self.users_group_id = gr_id
647 648 self.user_id = u_id
648 649
649 650
650 651 class Repository(Base, BaseModel):
651 652 __tablename__ = 'repositories'
652 653 __table_args__ = (
653 654 UniqueConstraint('repo_name'),
654 655 Index('r_repo_name_idx', 'repo_name'),
655 656 {'extend_existing': True, 'mysql_engine': 'InnoDB',
656 657 'mysql_charset': 'utf8'},
657 658 )
658 659
659 660 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 661 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
661 662 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
662 663 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
663 664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
664 665 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
665 666 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
666 667 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
667 668 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
668 669 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
669 670 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
670 671 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
671 672 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
672 673 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
673 674 #changeset_cache = Column("changeset_cache", LargeBinary(), nullable=False) #JSON data
674 675
675 676 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
676 677 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
677 678
678 679 user = relationship('User')
679 680 fork = relationship('Repository', remote_side=repo_id)
680 681 group = relationship('RepoGroup')
681 682 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
682 683 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
683 684 stats = relationship('Statistics', cascade='all', uselist=False)
684 685
685 686 followers = relationship('UserFollowing',
686 687 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
687 688 cascade='all')
688 689
689 690 logs = relationship('UserLog')
690 691 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
691 692
692 693 pull_requests_org = relationship('PullRequest',
693 694 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
694 695 cascade="all, delete, delete-orphan")
695 696
696 697 pull_requests_other = relationship('PullRequest',
697 698 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
698 699 cascade="all, delete, delete-orphan")
699 700
700 701 def __unicode__(self):
701 702 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
702 703 self.repo_name)
703 704
704 705 @hybrid_property
705 706 def locked(self):
706 707 # always should return [user_id, timelocked]
707 708 if self._locked:
708 709 _lock_info = self._locked.split(':')
709 710 return int(_lock_info[0]), _lock_info[1]
710 711 return [None, None]
711 712
712 713 @locked.setter
713 714 def locked(self, val):
714 715 if val and isinstance(val, (list, tuple)):
715 716 self._locked = ':'.join(map(str, val))
716 717 else:
717 718 self._locked = None
718 719
719 720 @classmethod
720 721 def url_sep(cls):
721 722 return URL_SEP
722 723
723 724 @classmethod
724 725 def get_by_repo_name(cls, repo_name):
725 726 q = Session().query(cls).filter(cls.repo_name == repo_name)
726 727 q = q.options(joinedload(Repository.fork))\
727 728 .options(joinedload(Repository.user))\
728 729 .options(joinedload(Repository.group))
729 730 return q.scalar()
730 731
731 732 @classmethod
732 733 def get_by_full_path(cls, repo_full_path):
733 734 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
734 735 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
735 736
736 737 @classmethod
737 738 def get_repo_forks(cls, repo_id):
738 739 return cls.query().filter(Repository.fork_id == repo_id)
739 740
740 741 @classmethod
741 742 def base_path(cls):
742 743 """
743 744 Returns base path when all repos are stored
744 745
745 746 :param cls:
746 747 """
747 748 q = Session().query(RhodeCodeUi)\
748 749 .filter(RhodeCodeUi.ui_key == cls.url_sep())
749 750 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
750 751 return q.one().ui_value
751 752
752 753 @property
753 754 def forks(self):
754 755 """
755 756 Return forks of this repo
756 757 """
757 758 return Repository.get_repo_forks(self.repo_id)
758 759
759 760 @property
760 761 def parent(self):
761 762 """
762 763 Returns fork parent
763 764 """
764 765 return self.fork
765 766
766 767 @property
767 768 def just_name(self):
768 769 return self.repo_name.split(Repository.url_sep())[-1]
769 770
770 771 @property
771 772 def groups_with_parents(self):
772 773 groups = []
773 774 if self.group is None:
774 775 return groups
775 776
776 777 cur_gr = self.group
777 778 groups.insert(0, cur_gr)
778 779 while 1:
779 780 gr = getattr(cur_gr, 'parent_group', None)
780 781 cur_gr = cur_gr.parent_group
781 782 if gr is None:
782 783 break
783 784 groups.insert(0, gr)
784 785
785 786 return groups
786 787
787 788 @property
788 789 def groups_and_repo(self):
789 790 return self.groups_with_parents, self.just_name
790 791
791 792 @LazyProperty
792 793 def repo_path(self):
793 794 """
794 795 Returns base full path for that repository means where it actually
795 796 exists on a filesystem
796 797 """
797 798 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
798 799 Repository.url_sep())
799 800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
800 801 return q.one().ui_value
801 802
802 803 @property
803 804 def repo_full_path(self):
804 805 p = [self.repo_path]
805 806 # we need to split the name by / since this is how we store the
806 807 # names in the database, but that eventually needs to be converted
807 808 # into a valid system path
808 809 p += self.repo_name.split(Repository.url_sep())
809 810 return os.path.join(*p)
810 811
811 812 @property
812 813 def cache_keys(self):
813 814 """
814 815 Returns associated cache keys for that repo
815 816 """
816 817 return CacheInvalidation.query()\
817 818 .filter(CacheInvalidation.cache_args == self.repo_name)\
818 819 .order_by(CacheInvalidation.cache_key)\
819 820 .all()
820 821
821 822 def get_new_name(self, repo_name):
822 823 """
823 824 returns new full repository name based on assigned group and new new
824 825
825 826 :param group_name:
826 827 """
827 828 path_prefix = self.group.full_path_splitted if self.group else []
828 829 return Repository.url_sep().join(path_prefix + [repo_name])
829 830
830 831 @property
831 832 def _ui(self):
832 833 """
833 834 Creates an db based ui object for this repository
834 835 """
835 836 from rhodecode.lib.utils import make_ui
836 837 return make_ui('db', clear_session=False)
837 838
838 839 @classmethod
839 840 def inject_ui(cls, repo, extras={}):
840 841 from rhodecode.lib.vcs.backends.hg import MercurialRepository
841 842 from rhodecode.lib.vcs.backends.git import GitRepository
842 843 required = (MercurialRepository, GitRepository)
843 844 if not isinstance(repo, required):
844 845 raise Exception('repo must be instance of %s' % required)
845 846
846 847 # inject ui extra param to log this action via push logger
847 848 for k, v in extras.items():
848 849 repo._repo.ui.setconfig('rhodecode_extras', k, v)
849 850
850 851 @classmethod
851 852 def is_valid(cls, repo_name):
852 853 """
853 854 returns True if given repo name is a valid filesystem repository
854 855
855 856 :param cls:
856 857 :param repo_name:
857 858 """
858 859 from rhodecode.lib.utils import is_valid_repo
859 860
860 861 return is_valid_repo(repo_name, cls.base_path())
861 862
862 863 def get_api_data(self):
863 864 """
864 865 Common function for generating repo api data
865 866
866 867 """
867 868 repo = self
868 869 data = dict(
869 870 repo_id=repo.repo_id,
870 871 repo_name=repo.repo_name,
871 872 repo_type=repo.repo_type,
872 873 clone_uri=repo.clone_uri,
873 874 private=repo.private,
874 875 created_on=repo.created_on,
875 876 description=repo.description,
876 877 landing_rev=repo.landing_rev,
877 878 owner=repo.user.username,
878 879 fork_of=repo.fork.repo_name if repo.fork else None,
879 880 enable_statistics=repo.enable_statistics,
880 881 enable_locking=repo.enable_locking,
881 882 enable_downloads=repo.enable_downloads
882 883 )
883 884
884 885 return data
885 886
886 887 @classmethod
887 888 def lock(cls, repo, user_id):
888 889 repo.locked = [user_id, time.time()]
889 890 Session().add(repo)
890 891 Session().commit()
891 892
892 893 @classmethod
893 894 def unlock(cls, repo):
894 895 repo.locked = None
895 896 Session().add(repo)
896 897 Session().commit()
897 898
898 899 @property
899 900 def last_db_change(self):
900 901 return self.updated_on
901 902
902 903 #==========================================================================
903 904 # SCM PROPERTIES
904 905 #==========================================================================
905 906
906 907 def get_changeset(self, rev=None):
907 908 return get_changeset_safe(self.scm_instance, rev)
908 909
909 910 def get_landing_changeset(self):
910 911 """
911 912 Returns landing changeset, or if that doesn't exist returns the tip
912 913 """
913 914 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
914 915 return cs
915 916
916 917 def update_last_change(self, last_change=None):
917 918 if last_change is None:
918 919 last_change = datetime.datetime.now()
919 920 if self.updated_on is None or self.updated_on != last_change:
920 921 log.debug('updated repo %s with new date %s' % (self, last_change))
921 922 self.updated_on = last_change
922 923 Session().add(self)
923 924 Session().commit()
924 925
925 926 @property
926 927 def tip(self):
927 928 return self.get_changeset('tip')
928 929
929 930 @property
930 931 def author(self):
931 932 return self.tip.author
932 933
933 934 @property
934 935 def last_change(self):
935 936 return self.scm_instance.last_change
936 937
937 938 def get_comments(self, revisions=None):
938 939 """
939 940 Returns comments for this repository grouped by revisions
940 941
941 942 :param revisions: filter query by revisions only
942 943 """
943 944 cmts = ChangesetComment.query()\
944 945 .filter(ChangesetComment.repo == self)
945 946 if revisions:
946 947 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
947 948 grouped = defaultdict(list)
948 949 for cmt in cmts.all():
949 950 grouped[cmt.revision].append(cmt)
950 951 return grouped
951 952
952 953 def statuses(self, revisions=None):
953 954 """
954 955 Returns statuses for this repository
955 956
956 957 :param revisions: list of revisions to get statuses for
957 958 :type revisions: list
958 959 """
959 960
960 961 statuses = ChangesetStatus.query()\
961 962 .filter(ChangesetStatus.repo == self)\
962 963 .filter(ChangesetStatus.version == 0)
963 964 if revisions:
964 965 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
965 966 grouped = {}
966 967
967 968 #maybe we have open new pullrequest without a status ?
968 969 stat = ChangesetStatus.STATUS_UNDER_REVIEW
969 970 status_lbl = ChangesetStatus.get_status_lbl(stat)
970 971 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
971 972 for rev in pr.revisions:
972 973 pr_id = pr.pull_request_id
973 974 pr_repo = pr.other_repo.repo_name
974 975 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
975 976
976 977 for stat in statuses.all():
977 978 pr_id = pr_repo = None
978 979 if stat.pull_request:
979 980 pr_id = stat.pull_request.pull_request_id
980 981 pr_repo = stat.pull_request.other_repo.repo_name
981 982 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
982 983 pr_id, pr_repo]
983 984 return grouped
984 985
985 986 #==========================================================================
986 987 # SCM CACHE INSTANCE
987 988 #==========================================================================
988 989
989 990 @property
990 991 def invalidate(self):
991 992 return CacheInvalidation.invalidate(self.repo_name)
992 993
993 994 def set_invalidate(self):
994 995 """
995 996 set a cache for invalidation for this instance
996 997 """
997 998 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
998 999
999 1000 @LazyProperty
1000 1001 def scm_instance(self):
1001 1002 import rhodecode
1002 1003 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1003 1004 if full_cache:
1004 1005 return self.scm_instance_cached()
1005 1006 return self.__get_instance()
1006 1007
1007 1008 def scm_instance_cached(self, cache_map=None):
1008 1009 @cache_region('long_term')
1009 1010 def _c(repo_name):
1010 1011 return self.__get_instance()
1011 1012 rn = self.repo_name
1012 1013 log.debug('Getting cached instance of repo')
1013 1014
1014 1015 if cache_map:
1015 1016 # get using prefilled cache_map
1016 1017 invalidate_repo = cache_map[self.repo_name]
1017 1018 if invalidate_repo:
1018 1019 invalidate_repo = (None if invalidate_repo.cache_active
1019 1020 else invalidate_repo)
1020 1021 else:
1021 1022 # get from invalidate
1022 1023 invalidate_repo = self.invalidate
1023 1024
1024 1025 if invalidate_repo is not None:
1025 1026 region_invalidate(_c, None, rn)
1026 1027 # update our cache
1027 1028 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1028 1029 return _c(rn)
1029 1030
1030 1031 def __get_instance(self):
1031 1032 repo_full_path = self.repo_full_path
1032 1033 try:
1033 1034 alias = get_scm(repo_full_path)[0]
1034 1035 log.debug('Creating instance of %s repository' % alias)
1035 1036 backend = get_backend(alias)
1036 1037 except VCSError:
1037 1038 log.error(traceback.format_exc())
1038 1039 log.error('Perhaps this repository is in db and not in '
1039 1040 'filesystem run rescan repositories with '
1040 1041 '"destroy old data " option from admin panel')
1041 1042 return
1042 1043
1043 1044 if alias == 'hg':
1044 1045
1045 1046 repo = backend(safe_str(repo_full_path), create=False,
1046 1047 baseui=self._ui)
1047 1048 # skip hidden web repository
1048 1049 if repo._get_hidden():
1049 1050 return
1050 1051 else:
1051 1052 repo = backend(repo_full_path, create=False)
1052 1053
1053 1054 return repo
1054 1055
1055 1056
1056 1057 class RepoGroup(Base, BaseModel):
1057 1058 __tablename__ = 'groups'
1058 1059 __table_args__ = (
1059 1060 UniqueConstraint('group_name', 'group_parent_id'),
1060 1061 CheckConstraint('group_id != group_parent_id'),
1061 1062 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1062 1063 'mysql_charset': 'utf8'},
1063 1064 )
1064 1065 __mapper_args__ = {'order_by': 'group_name'}
1065 1066
1066 1067 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1067 1068 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1068 1069 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1069 1070 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1070 1071 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1071 1072
1072 1073 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1073 1074 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1074 1075
1075 1076 parent_group = relationship('RepoGroup', remote_side=group_id)
1076 1077
1077 1078 def __init__(self, group_name='', parent_group=None):
1078 1079 self.group_name = group_name
1079 1080 self.parent_group = parent_group
1080 1081
1081 1082 def __unicode__(self):
1082 1083 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1083 1084 self.group_name)
1084 1085
1085 1086 @classmethod
1086 1087 def groups_choices(cls, check_perms=False):
1087 1088 from webhelpers.html import literal as _literal
1088 1089 from rhodecode.model.scm import ScmModel
1089 1090 groups = cls.query().all()
1090 1091 if check_perms:
1091 1092 #filter group user have access to, it's done
1092 1093 #magically inside ScmModel based on current user
1093 1094 groups = ScmModel().get_repos_groups(groups)
1094 1095 repo_groups = [('', '')]
1095 1096 sep = ' &raquo; '
1096 1097 _name = lambda k: _literal(sep.join(k))
1097 1098
1098 1099 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1099 1100 for x in groups])
1100 1101
1101 1102 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1102 1103 return repo_groups
1103 1104
1104 1105 @classmethod
1105 1106 def url_sep(cls):
1106 1107 return URL_SEP
1107 1108
1108 1109 @classmethod
1109 1110 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1110 1111 if case_insensitive:
1111 1112 gr = cls.query()\
1112 1113 .filter(cls.group_name.ilike(group_name))
1113 1114 else:
1114 1115 gr = cls.query()\
1115 1116 .filter(cls.group_name == group_name)
1116 1117 if cache:
1117 1118 gr = gr.options(FromCache(
1118 1119 "sql_cache_short",
1119 1120 "get_group_%s" % _hash_key(group_name)
1120 1121 )
1121 1122 )
1122 1123 return gr.scalar()
1123 1124
1124 1125 @property
1125 1126 def parents(self):
1126 1127 parents_recursion_limit = 5
1127 1128 groups = []
1128 1129 if self.parent_group is None:
1129 1130 return groups
1130 1131 cur_gr = self.parent_group
1131 1132 groups.insert(0, cur_gr)
1132 1133 cnt = 0
1133 1134 while 1:
1134 1135 cnt += 1
1135 1136 gr = getattr(cur_gr, 'parent_group', None)
1136 1137 cur_gr = cur_gr.parent_group
1137 1138 if gr is None:
1138 1139 break
1139 1140 if cnt == parents_recursion_limit:
1140 1141 # this will prevent accidental infinit loops
1141 1142 log.error('group nested more than %s' %
1142 1143 parents_recursion_limit)
1143 1144 break
1144 1145
1145 1146 groups.insert(0, gr)
1146 1147 return groups
1147 1148
1148 1149 @property
1149 1150 def children(self):
1150 1151 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1151 1152
1152 1153 @property
1153 1154 def name(self):
1154 1155 return self.group_name.split(RepoGroup.url_sep())[-1]
1155 1156
1156 1157 @property
1157 1158 def full_path(self):
1158 1159 return self.group_name
1159 1160
1160 1161 @property
1161 1162 def full_path_splitted(self):
1162 1163 return self.group_name.split(RepoGroup.url_sep())
1163 1164
1164 1165 @property
1165 1166 def repositories(self):
1166 1167 return Repository.query()\
1167 1168 .filter(Repository.group == self)\
1168 1169 .order_by(Repository.repo_name)
1169 1170
1170 1171 @property
1171 1172 def repositories_recursive_count(self):
1172 1173 cnt = self.repositories.count()
1173 1174
1174 1175 def children_count(group):
1175 1176 cnt = 0
1176 1177 for child in group.children:
1177 1178 cnt += child.repositories.count()
1178 1179 cnt += children_count(child)
1179 1180 return cnt
1180 1181
1181 1182 return cnt + children_count(self)
1182 1183
1183 1184 def recursive_groups_and_repos(self):
1184 1185 """
1185 1186 Recursive return all groups, with repositories in those groups
1186 1187 """
1187 1188 all_ = []
1188 1189
1189 1190 def _get_members(root_gr):
1190 1191 for r in root_gr.repositories:
1191 1192 all_.append(r)
1192 1193 childs = root_gr.children.all()
1193 1194 if childs:
1194 1195 for gr in childs:
1195 1196 all_.append(gr)
1196 1197 _get_members(gr)
1197 1198
1198 1199 _get_members(self)
1199 1200 return [self] + all_
1200 1201
1201 1202 def get_new_name(self, group_name):
1202 1203 """
1203 1204 returns new full group name based on parent and new name
1204 1205
1205 1206 :param group_name:
1206 1207 """
1207 1208 path_prefix = (self.parent_group.full_path_splitted if
1208 1209 self.parent_group else [])
1209 1210 return RepoGroup.url_sep().join(path_prefix + [group_name])
1210 1211
1211 1212
1212 1213 class Permission(Base, BaseModel):
1213 1214 __tablename__ = 'permissions'
1214 1215 __table_args__ = (
1215 1216 Index('p_perm_name_idx', 'permission_name'),
1216 1217 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1217 1218 'mysql_charset': 'utf8'},
1218 1219 )
1219 1220 PERMS = [
1220 1221 ('repository.none', _('Repository no access')),
1221 1222 ('repository.read', _('Repository read access')),
1222 1223 ('repository.write', _('Repository write access')),
1223 1224 ('repository.admin', _('Repository admin access')),
1224 1225
1225 1226 ('group.none', _('Repositories Group no access')),
1226 1227 ('group.read', _('Repositories Group read access')),
1227 1228 ('group.write', _('Repositories Group write access')),
1228 1229 ('group.admin', _('Repositories Group admin access')),
1229 1230
1230 1231 ('hg.admin', _('RhodeCode Administrator')),
1231 1232 ('hg.create.none', _('Repository creation disabled')),
1232 1233 ('hg.create.repository', _('Repository creation enabled')),
1233 1234 ('hg.fork.none', _('Repository forking disabled')),
1234 1235 ('hg.fork.repository', _('Repository forking enabled')),
1235 1236 ('hg.register.none', _('Register disabled')),
1236 1237 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1237 1238 'with manual activation')),
1238 1239
1239 1240 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1240 1241 'with auto activation')),
1241 1242 ]
1242 1243
1243 1244 # defines which permissions are more important higher the more important
1244 1245 PERM_WEIGHTS = {
1245 1246 'repository.none': 0,
1246 1247 'repository.read': 1,
1247 1248 'repository.write': 3,
1248 1249 'repository.admin': 4,
1249 1250
1250 1251 'group.none': 0,
1251 1252 'group.read': 1,
1252 1253 'group.write': 3,
1253 1254 'group.admin': 4,
1254 1255
1255 1256 'hg.fork.none': 0,
1256 1257 'hg.fork.repository': 1,
1257 1258 'hg.create.none': 0,
1258 1259 'hg.create.repository':1
1259 1260 }
1260 1261
1261 1262 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1262 1263 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1263 1264 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1264 1265
1265 1266 def __unicode__(self):
1266 1267 return u"<%s('%s:%s')>" % (
1267 1268 self.__class__.__name__, self.permission_id, self.permission_name
1268 1269 )
1269 1270
1270 1271 @classmethod
1271 1272 def get_by_key(cls, key):
1272 1273 return cls.query().filter(cls.permission_name == key).scalar()
1273 1274
1274 1275 @classmethod
1275 1276 def get_default_perms(cls, default_user_id):
1276 1277 q = Session().query(UserRepoToPerm, Repository, cls)\
1277 1278 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1278 1279 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1279 1280 .filter(UserRepoToPerm.user_id == default_user_id)
1280 1281
1281 1282 return q.all()
1282 1283
1283 1284 @classmethod
1284 1285 def get_default_group_perms(cls, default_user_id):
1285 1286 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1286 1287 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1287 1288 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1288 1289 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1289 1290
1290 1291 return q.all()
1291 1292
1292 1293
1293 1294 class UserRepoToPerm(Base, BaseModel):
1294 1295 __tablename__ = 'repo_to_perm'
1295 1296 __table_args__ = (
1296 1297 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1297 1298 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1298 1299 'mysql_charset': 'utf8'}
1299 1300 )
1300 1301 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1301 1302 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1302 1303 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1303 1304 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1304 1305
1305 1306 user = relationship('User')
1306 1307 repository = relationship('Repository')
1307 1308 permission = relationship('Permission')
1308 1309
1309 1310 @classmethod
1310 1311 def create(cls, user, repository, permission):
1311 1312 n = cls()
1312 1313 n.user = user
1313 1314 n.repository = repository
1314 1315 n.permission = permission
1315 1316 Session().add(n)
1316 1317 return n
1317 1318
1318 1319 def __unicode__(self):
1319 1320 return u'<user:%s => %s >' % (self.user, self.repository)
1320 1321
1321 1322
1322 1323 class UserToPerm(Base, BaseModel):
1323 1324 __tablename__ = 'user_to_perm'
1324 1325 __table_args__ = (
1325 1326 UniqueConstraint('user_id', 'permission_id'),
1326 1327 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1327 1328 'mysql_charset': 'utf8'}
1328 1329 )
1329 1330 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1330 1331 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1331 1332 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1332 1333
1333 1334 user = relationship('User')
1334 1335 permission = relationship('Permission', lazy='joined')
1335 1336
1336 1337
1337 1338 class UsersGroupRepoToPerm(Base, BaseModel):
1338 1339 __tablename__ = 'users_group_repo_to_perm'
1339 1340 __table_args__ = (
1340 1341 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1341 1342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1342 1343 'mysql_charset': 'utf8'}
1343 1344 )
1344 1345 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1345 1346 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1346 1347 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1347 1348 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1348 1349
1349 1350 users_group = relationship('UsersGroup')
1350 1351 permission = relationship('Permission')
1351 1352 repository = relationship('Repository')
1352 1353
1353 1354 @classmethod
1354 1355 def create(cls, users_group, repository, permission):
1355 1356 n = cls()
1356 1357 n.users_group = users_group
1357 1358 n.repository = repository
1358 1359 n.permission = permission
1359 1360 Session().add(n)
1360 1361 return n
1361 1362
1362 1363 def __unicode__(self):
1363 1364 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1364 1365
1365 1366
1366 1367 class UsersGroupToPerm(Base, BaseModel):
1367 1368 __tablename__ = 'users_group_to_perm'
1368 1369 __table_args__ = (
1369 1370 UniqueConstraint('users_group_id', 'permission_id',),
1370 1371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1371 1372 'mysql_charset': 'utf8'}
1372 1373 )
1373 1374 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1374 1375 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1375 1376 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1376 1377
1377 1378 users_group = relationship('UsersGroup')
1378 1379 permission = relationship('Permission')
1379 1380
1380 1381
1381 1382 class UserRepoGroupToPerm(Base, BaseModel):
1382 1383 __tablename__ = 'user_repo_group_to_perm'
1383 1384 __table_args__ = (
1384 1385 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1385 1386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1386 1387 'mysql_charset': 'utf8'}
1387 1388 )
1388 1389
1389 1390 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1390 1391 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1391 1392 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1392 1393 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1393 1394
1394 1395 user = relationship('User')
1395 1396 group = relationship('RepoGroup')
1396 1397 permission = relationship('Permission')
1397 1398
1398 1399
1399 1400 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1400 1401 __tablename__ = 'users_group_repo_group_to_perm'
1401 1402 __table_args__ = (
1402 1403 UniqueConstraint('users_group_id', 'group_id'),
1403 1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 1405 'mysql_charset': 'utf8'}
1405 1406 )
1406 1407
1407 1408 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1408 1409 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1409 1410 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1410 1411 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1411 1412
1412 1413 users_group = relationship('UsersGroup')
1413 1414 permission = relationship('Permission')
1414 1415 group = relationship('RepoGroup')
1415 1416
1416 1417
1417 1418 class Statistics(Base, BaseModel):
1418 1419 __tablename__ = 'statistics'
1419 1420 __table_args__ = (
1420 1421 UniqueConstraint('repository_id'),
1421 1422 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1422 1423 'mysql_charset': 'utf8'}
1423 1424 )
1424 1425 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1425 1426 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1426 1427 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1427 1428 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1428 1429 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1429 1430 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1430 1431
1431 1432 repository = relationship('Repository', single_parent=True)
1432 1433
1433 1434
1434 1435 class UserFollowing(Base, BaseModel):
1435 1436 __tablename__ = 'user_followings'
1436 1437 __table_args__ = (
1437 1438 UniqueConstraint('user_id', 'follows_repository_id'),
1438 1439 UniqueConstraint('user_id', 'follows_user_id'),
1439 1440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1440 1441 'mysql_charset': 'utf8'}
1441 1442 )
1442 1443
1443 1444 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1444 1445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1445 1446 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1446 1447 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1447 1448 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1448 1449
1449 1450 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1450 1451
1451 1452 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1452 1453 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1453 1454
1454 1455 @classmethod
1455 1456 def get_repo_followers(cls, repo_id):
1456 1457 return cls.query().filter(cls.follows_repo_id == repo_id)
1457 1458
1458 1459
1459 1460 class CacheInvalidation(Base, BaseModel):
1460 1461 __tablename__ = 'cache_invalidation'
1461 1462 __table_args__ = (
1462 1463 UniqueConstraint('cache_key'),
1463 1464 Index('key_idx', 'cache_key'),
1464 1465 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 1466 'mysql_charset': 'utf8'},
1466 1467 )
1467 1468 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1468 1469 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1469 1470 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1470 1471 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1471 1472
1472 1473 def __init__(self, cache_key, cache_args=''):
1473 1474 self.cache_key = cache_key
1474 1475 self.cache_args = cache_args
1475 1476 self.cache_active = False
1476 1477
1477 1478 def __unicode__(self):
1478 1479 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1479 1480 self.cache_id, self.cache_key)
1480 1481
1481 1482 @property
1482 1483 def prefix(self):
1483 1484 _split = self.cache_key.split(self.cache_args, 1)
1484 1485 if _split and len(_split) == 2:
1485 1486 return _split[0]
1486 1487 return ''
1487 1488
1488 1489 @classmethod
1489 1490 def clear_cache(cls):
1490 1491 cls.query().delete()
1491 1492
1492 1493 @classmethod
1493 1494 def _get_key(cls, key):
1494 1495 """
1495 1496 Wrapper for generating a key, together with a prefix
1496 1497
1497 1498 :param key:
1498 1499 """
1499 1500 import rhodecode
1500 1501 prefix = ''
1501 1502 org_key = key
1502 1503 iid = rhodecode.CONFIG.get('instance_id')
1503 1504 if iid:
1504 1505 prefix = iid
1505 1506
1506 1507 return "%s%s" % (prefix, key), prefix, org_key
1507 1508
1508 1509 @classmethod
1509 1510 def get_by_key(cls, key):
1510 1511 return cls.query().filter(cls.cache_key == key).scalar()
1511 1512
1512 1513 @classmethod
1513 1514 def get_by_repo_name(cls, repo_name):
1514 1515 return cls.query().filter(cls.cache_args == repo_name).all()
1515 1516
1516 1517 @classmethod
1517 1518 def _get_or_create_key(cls, key, repo_name, commit=True):
1518 1519 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1519 1520 if not inv_obj:
1520 1521 try:
1521 1522 inv_obj = CacheInvalidation(key, repo_name)
1522 1523 Session().add(inv_obj)
1523 1524 if commit:
1524 1525 Session().commit()
1525 1526 except Exception:
1526 1527 log.error(traceback.format_exc())
1527 1528 Session().rollback()
1528 1529 return inv_obj
1529 1530
1530 1531 @classmethod
1531 1532 def invalidate(cls, key):
1532 1533 """
1533 1534 Returns Invalidation object if this given key should be invalidated
1534 1535 None otherwise. `cache_active = False` means that this cache
1535 1536 state is not valid and needs to be invalidated
1536 1537
1537 1538 :param key:
1538 1539 """
1539 1540 repo_name = key
1540 1541 repo_name = remove_suffix(repo_name, '_README')
1541 1542 repo_name = remove_suffix(repo_name, '_RSS')
1542 1543 repo_name = remove_suffix(repo_name, '_ATOM')
1543 1544
1544 1545 # adds instance prefix
1545 1546 key, _prefix, _org_key = cls._get_key(key)
1546 1547 inv = cls._get_or_create_key(key, repo_name)
1547 1548
1548 1549 if inv and inv.cache_active is False:
1549 1550 return inv
1550 1551
1551 1552 @classmethod
1552 1553 def set_invalidate(cls, key=None, repo_name=None):
1553 1554 """
1554 1555 Mark this Cache key for invalidation, either by key or whole
1555 1556 cache sets based on repo_name
1556 1557
1557 1558 :param key:
1558 1559 """
1559 1560 if key:
1560 1561 key, _prefix, _org_key = cls._get_key(key)
1561 1562 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1562 1563 elif repo_name:
1563 1564 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1564 1565
1565 1566 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1566 1567 % (len(inv_objs), key, repo_name))
1567 1568 try:
1568 1569 for inv_obj in inv_objs:
1569 1570 inv_obj.cache_active = False
1570 1571 Session().add(inv_obj)
1571 1572 Session().commit()
1572 1573 except Exception:
1573 1574 log.error(traceback.format_exc())
1574 1575 Session().rollback()
1575 1576
1576 1577 @classmethod
1577 1578 def set_valid(cls, key):
1578 1579 """
1579 1580 Mark this cache key as active and currently cached
1580 1581
1581 1582 :param key:
1582 1583 """
1583 1584 inv_obj = cls.get_by_key(key)
1584 1585 inv_obj.cache_active = True
1585 1586 Session().add(inv_obj)
1586 1587 Session().commit()
1587 1588
1588 1589 @classmethod
1589 1590 def get_cache_map(cls):
1590 1591
1591 1592 class cachemapdict(dict):
1592 1593
1593 1594 def __init__(self, *args, **kwargs):
1594 1595 fixkey = kwargs.get('fixkey')
1595 1596 if fixkey:
1596 1597 del kwargs['fixkey']
1597 1598 self.fixkey = fixkey
1598 1599 super(cachemapdict, self).__init__(*args, **kwargs)
1599 1600
1600 1601 def __getattr__(self, name):
1601 1602 key = name
1602 1603 if self.fixkey:
1603 1604 key, _prefix, _org_key = cls._get_key(key)
1604 1605 if key in self.__dict__:
1605 1606 return self.__dict__[key]
1606 1607 else:
1607 1608 return self[key]
1608 1609
1609 1610 def __getitem__(self, key):
1610 1611 if self.fixkey:
1611 1612 key, _prefix, _org_key = cls._get_key(key)
1612 1613 try:
1613 1614 return super(cachemapdict, self).__getitem__(key)
1614 1615 except KeyError:
1615 1616 return
1616 1617
1617 1618 cache_map = cachemapdict(fixkey=True)
1618 1619 for obj in cls.query().all():
1619 1620 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1620 1621 return cache_map
1621 1622
1622 1623
1623 1624 class ChangesetComment(Base, BaseModel):
1624 1625 __tablename__ = 'changeset_comments'
1625 1626 __table_args__ = (
1626 1627 Index('cc_revision_idx', 'revision'),
1627 1628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1628 1629 'mysql_charset': 'utf8'},
1629 1630 )
1630 1631 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1631 1632 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1632 1633 revision = Column('revision', String(40), nullable=True)
1633 1634 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1634 1635 line_no = Column('line_no', Unicode(10), nullable=True)
1635 1636 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1636 1637 f_path = Column('f_path', Unicode(1000), nullable=True)
1637 1638 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1638 1639 text = Column('text', UnicodeText(25000), nullable=False)
1639 1640 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1640 1641 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1641 1642
1642 1643 author = relationship('User', lazy='joined')
1643 1644 repo = relationship('Repository')
1644 1645 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1645 1646 pull_request = relationship('PullRequest', lazy='joined')
1646 1647
1647 1648 @classmethod
1648 1649 def get_users(cls, revision=None, pull_request_id=None):
1649 1650 """
1650 1651 Returns user associated with this ChangesetComment. ie those
1651 1652 who actually commented
1652 1653
1653 1654 :param cls:
1654 1655 :param revision:
1655 1656 """
1656 1657 q = Session().query(User)\
1657 1658 .join(ChangesetComment.author)
1658 1659 if revision:
1659 1660 q = q.filter(cls.revision == revision)
1660 1661 elif pull_request_id:
1661 1662 q = q.filter(cls.pull_request_id == pull_request_id)
1662 1663 return q.all()
1663 1664
1664 1665
1665 1666 class ChangesetStatus(Base, BaseModel):
1666 1667 __tablename__ = 'changeset_statuses'
1667 1668 __table_args__ = (
1668 1669 Index('cs_revision_idx', 'revision'),
1669 1670 Index('cs_version_idx', 'version'),
1670 1671 UniqueConstraint('repo_id', 'revision', 'version'),
1671 1672 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1672 1673 'mysql_charset': 'utf8'}
1673 1674 )
1674 1675 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1675 1676 STATUS_APPROVED = 'approved'
1676 1677 STATUS_REJECTED = 'rejected'
1677 1678 STATUS_UNDER_REVIEW = 'under_review'
1678 1679
1679 1680 STATUSES = [
1680 1681 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1681 1682 (STATUS_APPROVED, _("Approved")),
1682 1683 (STATUS_REJECTED, _("Rejected")),
1683 1684 (STATUS_UNDER_REVIEW, _("Under Review")),
1684 1685 ]
1685 1686
1686 1687 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1687 1688 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1688 1689 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1689 1690 revision = Column('revision', String(40), nullable=False)
1690 1691 status = Column('status', String(128), nullable=False, default=DEFAULT)
1691 1692 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1692 1693 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1693 1694 version = Column('version', Integer(), nullable=False, default=0)
1694 1695 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1695 1696
1696 1697 author = relationship('User', lazy='joined')
1697 1698 repo = relationship('Repository')
1698 1699 comment = relationship('ChangesetComment', lazy='joined')
1699 1700 pull_request = relationship('PullRequest', lazy='joined')
1700 1701
1701 1702 def __unicode__(self):
1702 1703 return u"<%s('%s:%s')>" % (
1703 1704 self.__class__.__name__,
1704 1705 self.status, self.author
1705 1706 )
1706 1707
1707 1708 @classmethod
1708 1709 def get_status_lbl(cls, value):
1709 1710 return dict(cls.STATUSES).get(value)
1710 1711
1711 1712 @property
1712 1713 def status_lbl(self):
1713 1714 return ChangesetStatus.get_status_lbl(self.status)
1714 1715
1715 1716
1716 1717 class PullRequest(Base, BaseModel):
1717 1718 __tablename__ = 'pull_requests'
1718 1719 __table_args__ = (
1719 1720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1720 1721 'mysql_charset': 'utf8'},
1721 1722 )
1722 1723
1723 1724 STATUS_NEW = u'new'
1724 1725 STATUS_OPEN = u'open'
1725 1726 STATUS_CLOSED = u'closed'
1726 1727
1727 1728 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1728 1729 title = Column('title', Unicode(256), nullable=True)
1729 1730 description = Column('description', UnicodeText(10240), nullable=True)
1730 1731 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1731 1732 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1732 1733 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1733 1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1734 1735 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1735 1736 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1736 1737 org_ref = Column('org_ref', Unicode(256), nullable=False)
1737 1738 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1738 1739 other_ref = Column('other_ref', Unicode(256), nullable=False)
1739 1740
1740 1741 @hybrid_property
1741 1742 def revisions(self):
1742 1743 return self._revisions.split(':')
1743 1744
1744 1745 @revisions.setter
1745 1746 def revisions(self, val):
1746 1747 self._revisions = ':'.join(val)
1747 1748
1748 1749 author = relationship('User', lazy='joined')
1749 1750 reviewers = relationship('PullRequestReviewers',
1750 1751 cascade="all, delete, delete-orphan")
1751 1752 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1752 1753 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1753 1754 statuses = relationship('ChangesetStatus')
1754 1755 comments = relationship('ChangesetComment',
1755 1756 cascade="all, delete, delete-orphan")
1756 1757
1757 1758 def is_closed(self):
1758 1759 return self.status == self.STATUS_CLOSED
1759 1760
1760 1761 def __json__(self):
1761 1762 return dict(
1762 1763 revisions=self.revisions
1763 1764 )
1764 1765
1765 1766
1766 1767 class PullRequestReviewers(Base, BaseModel):
1767 1768 __tablename__ = 'pull_request_reviewers'
1768 1769 __table_args__ = (
1769 1770 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1770 1771 'mysql_charset': 'utf8'},
1771 1772 )
1772 1773
1773 1774 def __init__(self, user=None, pull_request=None):
1774 1775 self.user = user
1775 1776 self.pull_request = pull_request
1776 1777
1777 1778 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1778 1779 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1779 1780 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1780 1781
1781 1782 user = relationship('User')
1782 1783 pull_request = relationship('PullRequest')
1783 1784
1784 1785
1785 1786 class Notification(Base, BaseModel):
1786 1787 __tablename__ = 'notifications'
1787 1788 __table_args__ = (
1788 1789 Index('notification_type_idx', 'type'),
1789 1790 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1790 1791 'mysql_charset': 'utf8'},
1791 1792 )
1792 1793
1793 1794 TYPE_CHANGESET_COMMENT = u'cs_comment'
1794 1795 TYPE_MESSAGE = u'message'
1795 1796 TYPE_MENTION = u'mention'
1796 1797 TYPE_REGISTRATION = u'registration'
1797 1798 TYPE_PULL_REQUEST = u'pull_request'
1798 1799 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1799 1800
1800 1801 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1801 1802 subject = Column('subject', Unicode(512), nullable=True)
1802 1803 body = Column('body', UnicodeText(50000), nullable=True)
1803 1804 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1804 1805 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1805 1806 type_ = Column('type', Unicode(256))
1806 1807
1807 1808 created_by_user = relationship('User')
1808 1809 notifications_to_users = relationship('UserNotification', lazy='joined',
1809 1810 cascade="all, delete, delete-orphan")
1810 1811
1811 1812 @property
1812 1813 def recipients(self):
1813 1814 return [x.user for x in UserNotification.query()\
1814 1815 .filter(UserNotification.notification == self)\
1815 1816 .order_by(UserNotification.user_id.asc()).all()]
1816 1817
1817 1818 @classmethod
1818 1819 def create(cls, created_by, subject, body, recipients, type_=None):
1819 1820 if type_ is None:
1820 1821 type_ = Notification.TYPE_MESSAGE
1821 1822
1822 1823 notification = cls()
1823 1824 notification.created_by_user = created_by
1824 1825 notification.subject = subject
1825 1826 notification.body = body
1826 1827 notification.type_ = type_
1827 1828 notification.created_on = datetime.datetime.now()
1828 1829
1829 1830 for u in recipients:
1830 1831 assoc = UserNotification()
1831 1832 assoc.notification = notification
1832 1833 u.notifications.append(assoc)
1833 1834 Session().add(notification)
1834 1835 return notification
1835 1836
1836 1837 @property
1837 1838 def description(self):
1838 1839 from rhodecode.model.notification import NotificationModel
1839 1840 return NotificationModel().make_description(self)
1840 1841
1841 1842
1842 1843 class UserNotification(Base, BaseModel):
1843 1844 __tablename__ = 'user_to_notification'
1844 1845 __table_args__ = (
1845 1846 UniqueConstraint('user_id', 'notification_id'),
1846 1847 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1847 1848 'mysql_charset': 'utf8'}
1848 1849 )
1849 1850 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1850 1851 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1851 1852 read = Column('read', Boolean, default=False)
1852 1853 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1853 1854
1854 1855 user = relationship('User', lazy="joined")
1855 1856 notification = relationship('Notification', lazy="joined",
1856 1857 order_by=lambda: Notification.created_on.desc(),)
1857 1858
1858 1859 def mark_as_read(self):
1859 1860 self.read = True
1860 1861 Session().add(self)
1861 1862
1862 1863
1863 1864 class DbMigrateVersion(Base, BaseModel):
1864 1865 __tablename__ = 'db_migrate_version'
1865 1866 __table_args__ = (
1866 1867 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1867 1868 'mysql_charset': 'utf8'},
1868 1869 )
1869 1870 repository_id = Column('repository_id', String(250), primary_key=True)
1870 1871 repository_path = Column('repository_path', Text)
1871 1872 version = Column('version', Integer)
@@ -1,737 +1,736 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import itertools
29 29 import collections
30 import functools
31 30 from pylons import url
32 31 from pylons.i18n.translation import _
33 32
34 33 from sqlalchemy.exc import DatabaseError
35 34 from sqlalchemy.orm import joinedload
36 35
37 36 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
38 37 from rhodecode.lib.caching_query import FromCache
39 38 from rhodecode.model import BaseModel
40 39 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
41 40 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
42 41 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \
43 42 UserEmailMap, UserIpMap
44 43 from rhodecode.lib.exceptions import DefaultUserException, \
45 44 UserOwnsReposException
46 45
47 46
48 47 log = logging.getLogger(__name__)
49 48
50 49 PERM_WEIGHTS = Permission.PERM_WEIGHTS
51 50
52 51
53 52 class UserModel(BaseModel):
54 53 cls = User
55 54
56 55 def get(self, user_id, cache=False):
57 56 user = self.sa.query(User)
58 57 if cache:
59 58 user = user.options(FromCache("sql_cache_short",
60 59 "get_user_%s" % user_id))
61 60 return user.get(user_id)
62 61
63 62 def get_user(self, user):
64 63 return self._get_user(user)
65 64
66 65 def get_by_username(self, username, cache=False, case_insensitive=False):
67 66
68 67 if case_insensitive:
69 68 user = self.sa.query(User).filter(User.username.ilike(username))
70 69 else:
71 70 user = self.sa.query(User)\
72 71 .filter(User.username == username)
73 72 if cache:
74 73 user = user.options(FromCache("sql_cache_short",
75 74 "get_user_%s" % username))
76 75 return user.scalar()
77 76
78 77 def get_by_email(self, email, cache=False, case_insensitive=False):
79 78 return User.get_by_email(email, case_insensitive, cache)
80 79
81 80 def get_by_api_key(self, api_key, cache=False):
82 81 return User.get_by_api_key(api_key, cache)
83 82
84 83 def create(self, form_data):
85 84 from rhodecode.lib.auth import get_crypt_password
86 85 try:
87 86 new_user = User()
88 87 for k, v in form_data.items():
89 88 if k == 'password':
90 89 v = get_crypt_password(v)
91 90 if k == 'firstname':
92 91 k = 'name'
93 92 setattr(new_user, k, v)
94 93
95 94 new_user.api_key = generate_api_key(form_data['username'])
96 95 self.sa.add(new_user)
97 96 return new_user
98 97 except:
99 98 log.error(traceback.format_exc())
100 99 raise
101 100
102 101 def create_or_update(self, username, password, email, firstname='',
103 102 lastname='', active=True, admin=False, ldap_dn=None):
104 103 """
105 104 Creates a new instance if not found, or updates current one
106 105
107 106 :param username:
108 107 :param password:
109 108 :param email:
110 109 :param active:
111 110 :param firstname:
112 111 :param lastname:
113 112 :param active:
114 113 :param admin:
115 114 :param ldap_dn:
116 115 """
117 116
118 117 from rhodecode.lib.auth import get_crypt_password
119 118
120 119 log.debug('Checking for %s account in RhodeCode database' % username)
121 120 user = User.get_by_username(username, case_insensitive=True)
122 121 if user is None:
123 122 log.debug('creating new user %s' % username)
124 123 new_user = User()
125 124 edit = False
126 125 else:
127 126 log.debug('updating user %s' % username)
128 127 new_user = user
129 128 edit = True
130 129
131 130 try:
132 131 new_user.username = username
133 132 new_user.admin = admin
134 133 # set password only if creating an user or password is changed
135 134 if edit is False or user.password != password:
136 135 new_user.password = get_crypt_password(password)
137 136 new_user.api_key = generate_api_key(username)
138 137 new_user.email = email
139 138 new_user.active = active
140 139 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
141 140 new_user.name = firstname
142 141 new_user.lastname = lastname
143 142 self.sa.add(new_user)
144 143 return new_user
145 144 except (DatabaseError,):
146 145 log.error(traceback.format_exc())
147 146 raise
148 147
149 148 def create_for_container_auth(self, username, attrs):
150 149 """
151 150 Creates the given user if it's not already in the database
152 151
153 152 :param username:
154 153 :param attrs:
155 154 """
156 155 if self.get_by_username(username, case_insensitive=True) is None:
157 156
158 157 # autogenerate email for container account without one
159 158 generate_email = lambda usr: '%s@container_auth.account' % usr
160 159
161 160 try:
162 161 new_user = User()
163 162 new_user.username = username
164 163 new_user.password = None
165 164 new_user.api_key = generate_api_key(username)
166 165 new_user.email = attrs['email']
167 166 new_user.active = attrs.get('active', True)
168 167 new_user.name = attrs['name'] or generate_email(username)
169 168 new_user.lastname = attrs['lastname']
170 169
171 170 self.sa.add(new_user)
172 171 return new_user
173 172 except (DatabaseError,):
174 173 log.error(traceback.format_exc())
175 174 self.sa.rollback()
176 175 raise
177 176 log.debug('User %s already exists. Skipping creation of account'
178 177 ' for container auth.', username)
179 178 return None
180 179
181 180 def create_ldap(self, username, password, user_dn, attrs):
182 181 """
183 182 Checks if user is in database, if not creates this user marked
184 183 as ldap user
185 184
186 185 :param username:
187 186 :param password:
188 187 :param user_dn:
189 188 :param attrs:
190 189 """
191 190 from rhodecode.lib.auth import get_crypt_password
192 191 log.debug('Checking for such ldap account in RhodeCode database')
193 192 if self.get_by_username(username, case_insensitive=True) is None:
194 193
195 194 # autogenerate email for ldap account without one
196 195 generate_email = lambda usr: '%s@ldap.account' % usr
197 196
198 197 try:
199 198 new_user = User()
200 199 username = username.lower()
201 200 # add ldap account always lowercase
202 201 new_user.username = username
203 202 new_user.password = get_crypt_password(password)
204 203 new_user.api_key = generate_api_key(username)
205 204 new_user.email = attrs['email'] or generate_email(username)
206 205 new_user.active = attrs.get('active', True)
207 206 new_user.ldap_dn = safe_unicode(user_dn)
208 207 new_user.name = attrs['name']
209 208 new_user.lastname = attrs['lastname']
210 209
211 210 self.sa.add(new_user)
212 211 return new_user
213 212 except (DatabaseError,):
214 213 log.error(traceback.format_exc())
215 214 self.sa.rollback()
216 215 raise
217 216 log.debug('this %s user exists skipping creation of ldap account',
218 217 username)
219 218 return None
220 219
221 220 def create_registration(self, form_data):
222 221 from rhodecode.model.notification import NotificationModel
223 222
224 223 try:
225 224 form_data['admin'] = False
226 225 new_user = self.create(form_data)
227 226
228 227 self.sa.add(new_user)
229 228 self.sa.flush()
230 229
231 230 # notification to admins
232 231 subject = _('new user registration')
233 232 body = ('New user registration\n'
234 233 '---------------------\n'
235 234 '- Username: %s\n'
236 235 '- Full Name: %s\n'
237 236 '- Email: %s\n')
238 237 body = body % (new_user.username, new_user.full_name,
239 238 new_user.email)
240 239 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
241 240 kw = {'registered_user_url': edit_url}
242 241 NotificationModel().create(created_by=new_user, subject=subject,
243 242 body=body, recipients=None,
244 243 type_=Notification.TYPE_REGISTRATION,
245 244 email_kwargs=kw)
246 245
247 246 except:
248 247 log.error(traceback.format_exc())
249 248 raise
250 249
251 250 def update(self, user_id, form_data, skip_attrs=[]):
252 251 from rhodecode.lib.auth import get_crypt_password
253 252 try:
254 253 user = self.get(user_id, cache=False)
255 254 if user.username == 'default':
256 255 raise DefaultUserException(
257 256 _("You can't Edit this user since it's"
258 257 " crucial for entire application"))
259 258
260 259 for k, v in form_data.items():
261 260 if k in skip_attrs:
262 261 continue
263 262 if k == 'new_password' and v:
264 263 user.password = get_crypt_password(v)
265 264 user.api_key = generate_api_key(user.username)
266 265 else:
267 266 if k == 'firstname':
268 267 k = 'name'
269 268 setattr(user, k, v)
270 269 self.sa.add(user)
271 270 except:
272 271 log.error(traceback.format_exc())
273 272 raise
274 273
275 274 def update_user(self, user, **kwargs):
276 275 from rhodecode.lib.auth import get_crypt_password
277 276 try:
278 277 user = self._get_user(user)
279 278 if user.username == 'default':
280 279 raise DefaultUserException(
281 280 _("You can't Edit this user since it's"
282 281 " crucial for entire application")
283 282 )
284 283
285 284 for k, v in kwargs.items():
286 285 if k == 'password' and v:
287 286 v = get_crypt_password(v)
288 287 user.api_key = generate_api_key(user.username)
289 288
290 289 setattr(user, k, v)
291 290 self.sa.add(user)
292 291 return user
293 292 except:
294 293 log.error(traceback.format_exc())
295 294 raise
296 295
297 296 def update_my_account(self, user_id, form_data):
298 297 from rhodecode.lib.auth import get_crypt_password
299 298 try:
300 299 user = self.get(user_id, cache=False)
301 300 if user.username == 'default':
302 301 raise DefaultUserException(
303 302 _("You can't Edit this user since it's"
304 303 " crucial for entire application")
305 304 )
306 305 for k, v in form_data.items():
307 306 if k == 'new_password' and v:
308 307 user.password = get_crypt_password(v)
309 308 user.api_key = generate_api_key(user.username)
310 309 else:
311 310 if k == 'firstname':
312 311 k = 'name'
313 312 if k not in ['admin', 'active']:
314 313 setattr(user, k, v)
315 314
316 315 self.sa.add(user)
317 316 except:
318 317 log.error(traceback.format_exc())
319 318 raise
320 319
321 320 def delete(self, user):
322 321 user = self._get_user(user)
323 322
324 323 try:
325 324 if user.username == 'default':
326 325 raise DefaultUserException(
327 326 _(u"You can't remove this user since it's"
328 327 " crucial for entire application")
329 328 )
330 329 if user.repositories:
331 330 repos = [x.repo_name for x in user.repositories]
332 331 raise UserOwnsReposException(
333 332 _(u'user "%s" still owns %s repositories and cannot be '
334 333 'removed. Switch owners or remove those repositories. %s')
335 334 % (user.username, len(repos), ', '.join(repos))
336 335 )
337 336 self.sa.delete(user)
338 337 except:
339 338 log.error(traceback.format_exc())
340 339 raise
341 340
342 341 def reset_password_link(self, data):
343 342 from rhodecode.lib.celerylib import tasks, run_task
344 343 run_task(tasks.send_password_link, data['email'])
345 344
346 345 def reset_password(self, data):
347 346 from rhodecode.lib.celerylib import tasks, run_task
348 347 run_task(tasks.reset_user_password, data['email'])
349 348
350 349 def fill_data(self, auth_user, user_id=None, api_key=None):
351 350 """
352 351 Fetches auth_user by user_id,or api_key if present.
353 352 Fills auth_user attributes with those taken from database.
354 353 Additionally set's is_authenitated if lookup fails
355 354 present in database
356 355
357 356 :param auth_user: instance of user to set attributes
358 357 :param user_id: user id to fetch by
359 358 :param api_key: api key to fetch by
360 359 """
361 360 if user_id is None and api_key is None:
362 361 raise Exception('You need to pass user_id or api_key')
363 362
364 363 try:
365 364 if api_key:
366 365 dbuser = self.get_by_api_key(api_key)
367 366 else:
368 367 dbuser = self.get(user_id)
369 368
370 369 if dbuser is not None and dbuser.active:
371 370 log.debug('filling %s data' % dbuser)
372 371 for k, v in dbuser.get_dict().items():
373 372 setattr(auth_user, k, v)
374 373 else:
375 374 return False
376 375
377 376 except:
378 377 log.error(traceback.format_exc())
379 378 auth_user.is_authenticated = False
380 379 return False
381 380
382 381 return True
383 382
384 383 def fill_perms(self, user, explicit=True, algo='higherwin'):
385 384 """
386 385 Fills user permission attribute with permissions taken from database
387 386 works for permissions given for repositories, and for permissions that
388 387 are granted to groups
389 388
390 389 :param user: user instance to fill his perms
391 390 :param explicit: In case there are permissions both for user and a group
392 391 that user is part of, explicit flag will defiine if user will
393 392 explicitly override permissions from group, if it's False it will
394 393 make decision based on the algo
395 394 :param algo: algorithm to decide what permission should be choose if
396 395 it's multiple defined, eg user in two different groups. It also
397 396 decides if explicit flag is turned off how to specify the permission
398 397 for case when user is in a group + have defined separate permission
399 398 """
400 399 RK = 'repositories'
401 400 GK = 'repositories_groups'
402 401 GLOBAL = 'global'
403 402 user.permissions[RK] = {}
404 403 user.permissions[GK] = {}
405 404 user.permissions[GLOBAL] = set()
406 405
407 406 def _choose_perm(new_perm, cur_perm):
408 407 new_perm_val = PERM_WEIGHTS[new_perm]
409 408 cur_perm_val = PERM_WEIGHTS[cur_perm]
410 409 if algo == 'higherwin':
411 410 if new_perm_val > cur_perm_val:
412 411 return new_perm
413 412 return cur_perm
414 413 elif algo == 'lowerwin':
415 414 if new_perm_val < cur_perm_val:
416 415 return new_perm
417 416 return cur_perm
418 417
419 418 #======================================================================
420 419 # fetch default permissions
421 420 #======================================================================
422 421 default_user = User.get_by_username('default', cache=True)
423 422 default_user_id = default_user.user_id
424 423
425 424 default_repo_perms = Permission.get_default_perms(default_user_id)
426 425 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
427 426
428 427 if user.is_admin:
429 428 #==================================================================
430 429 # admin user have all default rights for repositories
431 430 # and groups set to admin
432 431 #==================================================================
433 432 user.permissions[GLOBAL].add('hg.admin')
434 433
435 434 # repositories
436 435 for perm in default_repo_perms:
437 436 r_k = perm.UserRepoToPerm.repository.repo_name
438 437 p = 'repository.admin'
439 438 user.permissions[RK][r_k] = p
440 439
441 440 # repositories groups
442 441 for perm in default_repo_groups_perms:
443 442 rg_k = perm.UserRepoGroupToPerm.group.group_name
444 443 p = 'group.admin'
445 444 user.permissions[GK][rg_k] = p
446 445 return user
447 446
448 447 #==================================================================
449 448 # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS
450 449 #==================================================================
451 450 uid = user.user_id
452 451
453 452 # default global permissions taken fron the default user
454 453 default_global_perms = self.sa.query(UserToPerm)\
455 454 .filter(UserToPerm.user_id == default_user_id)
456 455
457 456 for perm in default_global_perms:
458 457 user.permissions[GLOBAL].add(perm.permission.permission_name)
459 458
460 459 # defaults for repositories, taken from default user
461 460 for perm in default_repo_perms:
462 461 r_k = perm.UserRepoToPerm.repository.repo_name
463 462 if perm.Repository.private and not (perm.Repository.user_id == uid):
464 463 # disable defaults for private repos,
465 464 p = 'repository.none'
466 465 elif perm.Repository.user_id == uid:
467 466 # set admin if owner
468 467 p = 'repository.admin'
469 468 else:
470 469 p = perm.Permission.permission_name
471 470
472 471 user.permissions[RK][r_k] = p
473 472
474 473 # defaults for repositories groups taken from default user permission
475 474 # on given group
476 475 for perm in default_repo_groups_perms:
477 476 rg_k = perm.UserRepoGroupToPerm.group.group_name
478 477 p = perm.Permission.permission_name
479 478 user.permissions[GK][rg_k] = p
480 479
481 480 #======================================================================
482 481 # !! OVERRIDE GLOBALS !! with user permissions if any found
483 482 #======================================================================
484 483 # those can be configured from groups or users explicitly
485 484 _configurable = set(['hg.fork.none', 'hg.fork.repository',
486 485 'hg.create.none', 'hg.create.repository'])
487 486
488 487 # USER GROUPS comes first
489 488 # users group global permissions
490 489 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
491 490 .options(joinedload(UsersGroupToPerm.permission))\
492 491 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
493 492 UsersGroupMember.users_group_id))\
494 493 .filter(UsersGroupMember.user_id == uid)\
495 494 .order_by(UsersGroupToPerm.users_group_id)\
496 495 .all()
497 496 #need to group here by groups since user can be in more than one group
498 497 _grouped = [[x, list(y)] for x, y in
499 498 itertools.groupby(user_perms_from_users_groups,
500 499 lambda x:x.users_group)]
501 500 for gr, perms in _grouped:
502 501 # since user can be in multiple groups iterate over them and
503 502 # select the lowest permissions first (more explicit)
504 503 ##TODO: do this^^
505 504 if not gr.inherit_default_permissions:
506 505 # NEED TO IGNORE all configurable permissions and
507 506 # replace them with explicitly set
508 507 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
509 508 .difference(_configurable)
510 509 for perm in perms:
511 510 user.permissions[GLOBAL].add(perm.permission.permission_name)
512 511
513 512 # user specific global permissions
514 513 user_perms = self.sa.query(UserToPerm)\
515 514 .options(joinedload(UserToPerm.permission))\
516 515 .filter(UserToPerm.user_id == uid).all()
517 516
518 517 if not user.inherit_default_permissions:
519 518 # NEED TO IGNORE all configurable permissions and
520 519 # replace them with explicitly set
521 520 user.permissions[GLOBAL] = user.permissions[GLOBAL]\
522 521 .difference(_configurable)
523 522
524 523 for perm in user_perms:
525 524 user.permissions[GLOBAL].add(perm.permission.permission_name)
526 525
527 526 #======================================================================
528 527 # !! PERMISSIONS FOR REPOSITORIES !!
529 528 #======================================================================
530 529 #======================================================================
531 530 # check if user is part of user groups for this repository and
532 531 # fill in his permission from it. _choose_perm decides of which
533 532 # permission should be selected based on selected method
534 533 #======================================================================
535 534
536 535 # users group for repositories permissions
537 536 user_repo_perms_from_users_groups = \
538 537 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
539 538 .join((Repository, UsersGroupRepoToPerm.repository_id ==
540 539 Repository.repo_id))\
541 540 .join((Permission, UsersGroupRepoToPerm.permission_id ==
542 541 Permission.permission_id))\
543 542 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
544 543 UsersGroupMember.users_group_id))\
545 544 .filter(UsersGroupMember.user_id == uid)\
546 545 .all()
547 546
548 547 multiple_counter = collections.defaultdict(int)
549 548 for perm in user_repo_perms_from_users_groups:
550 549 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
551 550 multiple_counter[r_k] += 1
552 551 p = perm.Permission.permission_name
553 552 cur_perm = user.permissions[RK][r_k]
554 553
555 554 if perm.Repository.user_id == uid:
556 555 # set admin if owner
557 556 p = 'repository.admin'
558 557 else:
559 558 if multiple_counter[r_k] > 1:
560 559 p = _choose_perm(p, cur_perm)
561 560 user.permissions[RK][r_k] = p
562 561
563 562 # user explicit permissions for repositories, overrides any specified
564 563 # by the group permission
565 564 user_repo_perms = \
566 565 self.sa.query(UserRepoToPerm, Permission, Repository)\
567 566 .join((Repository, UserRepoToPerm.repository_id ==
568 567 Repository.repo_id))\
569 568 .join((Permission, UserRepoToPerm.permission_id ==
570 569 Permission.permission_id))\
571 570 .filter(UserRepoToPerm.user_id == uid)\
572 571 .all()
573 572
574 573 for perm in user_repo_perms:
575 574 r_k = perm.UserRepoToPerm.repository.repo_name
576 575 cur_perm = user.permissions[RK][r_k]
577 576 # set admin if owner
578 577 if perm.Repository.user_id == uid:
579 578 p = 'repository.admin'
580 579 else:
581 580 p = perm.Permission.permission_name
582 581 if not explicit:
583 582 p = _choose_perm(p, cur_perm)
584 583 user.permissions[RK][r_k] = p
585 584
586 585 #======================================================================
587 586 # !! PERMISSIONS FOR REPOSITORIES GROUPS !!
588 587 #======================================================================
589 588 #======================================================================
590 589 # check if user is part of user groups for this repository groups and
591 590 # fill in his permission from it. _choose_perm decides of which
592 591 # permission should be selected based on selected method
593 592 #======================================================================
594 593 # users group for repo groups permissions
595 594 user_repo_group_perms_from_users_groups = \
596 595 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
597 596 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
598 597 .join((Permission, UsersGroupRepoGroupToPerm.permission_id
599 598 == Permission.permission_id))\
600 599 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id
601 600 == UsersGroupMember.users_group_id))\
602 601 .filter(UsersGroupMember.user_id == uid)\
603 602 .all()
604 603
605 604 multiple_counter = collections.defaultdict(int)
606 605 for perm in user_repo_group_perms_from_users_groups:
607 606 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
608 607 multiple_counter[g_k] += 1
609 608 p = perm.Permission.permission_name
610 609 cur_perm = user.permissions[GK][g_k]
611 610 if multiple_counter[g_k] > 1:
612 611 p = _choose_perm(p, cur_perm)
613 612 user.permissions[GK][g_k] = p
614 613
615 614 # user explicit permissions for repository groups
616 615 user_repo_groups_perms = \
617 616 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
618 617 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
619 618 .join((Permission, UserRepoGroupToPerm.permission_id
620 619 == Permission.permission_id))\
621 620 .filter(UserRepoGroupToPerm.user_id == uid)\
622 621 .all()
623 622
624 623 for perm in user_repo_groups_perms:
625 624 rg_k = perm.UserRepoGroupToPerm.group.group_name
626 625 p = perm.Permission.permission_name
627 626 cur_perm = user.permissions[GK][rg_k]
628 627 if not explicit:
629 628 p = _choose_perm(p, cur_perm)
630 629 user.permissions[GK][rg_k] = p
631 630
632 631 return user
633 632
634 633 def has_perm(self, user, perm):
635 634 perm = self._get_perm(perm)
636 635 user = self._get_user(user)
637 636
638 637 return UserToPerm.query().filter(UserToPerm.user == user)\
639 638 .filter(UserToPerm.permission == perm).scalar() is not None
640 639
641 640 def grant_perm(self, user, perm):
642 641 """
643 642 Grant user global permissions
644 643
645 644 :param user:
646 645 :param perm:
647 646 """
648 647 user = self._get_user(user)
649 648 perm = self._get_perm(perm)
650 649 # if this permission is already granted skip it
651 650 _perm = UserToPerm.query()\
652 651 .filter(UserToPerm.user == user)\
653 652 .filter(UserToPerm.permission == perm)\
654 653 .scalar()
655 654 if _perm:
656 655 return
657 656 new = UserToPerm()
658 657 new.user = user
659 658 new.permission = perm
660 659 self.sa.add(new)
661 660
662 661 def revoke_perm(self, user, perm):
663 662 """
664 663 Revoke users global permissions
665 664
666 665 :param user:
667 666 :param perm:
668 667 """
669 668 user = self._get_user(user)
670 669 perm = self._get_perm(perm)
671 670
672 671 obj = UserToPerm.query()\
673 672 .filter(UserToPerm.user == user)\
674 673 .filter(UserToPerm.permission == perm)\
675 674 .scalar()
676 675 if obj:
677 676 self.sa.delete(obj)
678 677
679 678 def add_extra_email(self, user, email):
680 679 """
681 680 Adds email address to UserEmailMap
682 681
683 682 :param user:
684 683 :param email:
685 684 """
686 685 from rhodecode.model import forms
687 686 form = forms.UserExtraEmailForm()()
688 687 data = form.to_python(dict(email=email))
689 688 user = self._get_user(user)
690 689
691 690 obj = UserEmailMap()
692 691 obj.user = user
693 692 obj.email = data['email']
694 693 self.sa.add(obj)
695 694 return obj
696 695
697 696 def delete_extra_email(self, user, email_id):
698 697 """
699 698 Removes email address from UserEmailMap
700 699
701 700 :param user:
702 701 :param email_id:
703 702 """
704 703 user = self._get_user(user)
705 704 obj = UserEmailMap.query().get(email_id)
706 705 if obj:
707 706 self.sa.delete(obj)
708 707
709 708 def add_extra_ip(self, user, ip):
710 709 """
711 710 Adds ip address to UserIpMap
712 711
713 712 :param user:
714 713 :param ip:
715 714 """
716 715 from rhodecode.model import forms
717 716 form = forms.UserExtraIpForm()()
718 717 data = form.to_python(dict(ip=ip))
719 718 user = self._get_user(user)
720 719
721 720 obj = UserIpMap()
722 721 obj.user = user
723 722 obj.ip_addr = data['ip']
724 723 self.sa.add(obj)
725 724 return obj
726 725
727 726 def delete_extra_ip(self, user, ip_id):
728 727 """
729 728 Removes ip address from UserIpMap
730 729
731 730 :param user:
732 731 :param ip_id:
733 732 """
734 733 user = self._get_user(user)
735 734 obj = UserIpMap.query().get(ip_id)
736 735 if obj:
737 736 self.sa.delete(obj)
@@ -1,4850 +1,4847 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 2 {
3 3 border: 0;
4 4 outline: 0;
5 5 font-size: 100%;
6 6 vertical-align: baseline;
7 7 background: transparent;
8 8 margin: 0;
9 9 padding: 0;
10 10 }
11 11
12 12 body {
13 13 line-height: 1;
14 14 height: 100%;
15 15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 18 color: #000;
19 19 margin: 0;
20 20 padding: 0;
21 21 font-size: 12px;
22 22 }
23 23
24 24 ol,ul {
25 25 list-style: none;
26 26 }
27 27
28 28 blockquote,q {
29 29 quotes: none;
30 30 }
31 31
32 32 blockquote:before,blockquote:after,q:before,q:after {
33 33 content: none;
34 34 }
35 35
36 36 :focus {
37 37 outline: 0;
38 38 }
39 39
40 40 del {
41 41 text-decoration: line-through;
42 42 }
43 43
44 44 table {
45 45 border-collapse: collapse;
46 46 border-spacing: 0;
47 47 }
48 48
49 49 html {
50 50 height: 100%;
51 51 }
52 52
53 53 a {
54 54 color: #003367;
55 55 text-decoration: none;
56 56 cursor: pointer;
57 57 }
58 58
59 59 a:hover {
60 60 color: #316293;
61 61 text-decoration: underline;
62 62 }
63 63
64 64 h1,h2,h3,h4,h5,h6,
65 65 div.h1,div.h2,div.h3,div.h4,div.h5,div.h6 {
66 66 color: #292929;
67 67 font-weight: 700;
68 68 }
69 69
70 70 h1,div.h1 {
71 71 font-size: 22px;
72 72 }
73 73
74 74 h2,div.h2 {
75 75 font-size: 20px;
76 76 }
77 77
78 78 h3,div.h3 {
79 79 font-size: 18px;
80 80 }
81 81
82 82 h4,div.h4 {
83 83 font-size: 16px;
84 84 }
85 85
86 86 h5,div.h5 {
87 87 font-size: 14px;
88 88 }
89 89
90 90 h6,div.h6 {
91 91 font-size: 11px;
92 92 }
93 93
94 94 ul.circle {
95 95 list-style-type: circle;
96 96 }
97 97
98 98 ul.disc {
99 99 list-style-type: disc;
100 100 }
101 101
102 102 ul.square {
103 103 list-style-type: square;
104 104 }
105 105
106 106 ol.lower-roman {
107 107 list-style-type: lower-roman;
108 108 }
109 109
110 110 ol.upper-roman {
111 111 list-style-type: upper-roman;
112 112 }
113 113
114 114 ol.lower-alpha {
115 115 list-style-type: lower-alpha;
116 116 }
117 117
118 118 ol.upper-alpha {
119 119 list-style-type: upper-alpha;
120 120 }
121 121
122 122 ol.decimal {
123 123 list-style-type: decimal;
124 124 }
125 125
126 126 div.color {
127 127 clear: both;
128 128 overflow: hidden;
129 129 position: absolute;
130 130 background: #FFF;
131 131 margin: 7px 0 0 60px;
132 132 padding: 1px 1px 1px 0;
133 133 }
134 134
135 135 div.color a {
136 136 width: 15px;
137 137 height: 15px;
138 138 display: block;
139 139 float: left;
140 140 margin: 0 0 0 1px;
141 141 padding: 0;
142 142 }
143 143
144 144 div.options {
145 145 clear: both;
146 146 overflow: hidden;
147 147 position: absolute;
148 148 background: #FFF;
149 149 margin: 7px 0 0 162px;
150 150 padding: 0;
151 151 }
152 152
153 153 div.options a {
154 154 height: 1%;
155 155 display: block;
156 156 text-decoration: none;
157 157 margin: 0;
158 158 padding: 3px 8px;
159 159 }
160 160
161 161 .top-left-rounded-corner {
162 162 -webkit-border-top-left-radius: 8px;
163 163 -khtml-border-radius-topleft: 8px;
164 164 -moz-border-radius-topleft: 8px;
165 165 border-top-left-radius: 8px;
166 166 }
167 167
168 168 .top-right-rounded-corner {
169 169 -webkit-border-top-right-radius: 8px;
170 170 -khtml-border-radius-topright: 8px;
171 171 -moz-border-radius-topright: 8px;
172 172 border-top-right-radius: 8px;
173 173 }
174 174
175 175 .bottom-left-rounded-corner {
176 176 -webkit-border-bottom-left-radius: 8px;
177 177 -khtml-border-radius-bottomleft: 8px;
178 178 -moz-border-radius-bottomleft: 8px;
179 179 border-bottom-left-radius: 8px;
180 180 }
181 181
182 182 .bottom-right-rounded-corner {
183 183 -webkit-border-bottom-right-radius: 8px;
184 184 -khtml-border-radius-bottomright: 8px;
185 185 -moz-border-radius-bottomright: 8px;
186 186 border-bottom-right-radius: 8px;
187 187 }
188 188
189 189 .top-left-rounded-corner-mid {
190 190 -webkit-border-top-left-radius: 4px;
191 191 -khtml-border-radius-topleft: 4px;
192 192 -moz-border-radius-topleft: 4px;
193 193 border-top-left-radius: 4px;
194 194 }
195 195
196 196 .top-right-rounded-corner-mid {
197 197 -webkit-border-top-right-radius: 4px;
198 198 -khtml-border-radius-topright: 4px;
199 199 -moz-border-radius-topright: 4px;
200 200 border-top-right-radius: 4px;
201 201 }
202 202
203 203 .bottom-left-rounded-corner-mid {
204 204 -webkit-border-bottom-left-radius: 4px;
205 205 -khtml-border-radius-bottomleft: 4px;
206 206 -moz-border-radius-bottomleft: 4px;
207 207 border-bottom-left-radius: 4px;
208 208 }
209 209
210 210 .bottom-right-rounded-corner-mid {
211 211 -webkit-border-bottom-right-radius: 4px;
212 212 -khtml-border-radius-bottomright: 4px;
213 213 -moz-border-radius-bottomright: 4px;
214 214 border-bottom-right-radius: 4px;
215 215 }
216 216
217 217 .help-block {
218 218 color: #999999;
219 219 display: block;
220 220 margin-bottom: 0;
221 221 margin-top: 5px;
222 222 }
223 223
224 224 .empty_data{
225 225 color:#B9B9B9;
226 226 }
227 227
228 228 a.permalink{
229 229 visibility: hidden;
230 230 }
231 231
232 232 a.permalink:hover{
233 233 text-decoration: none;
234 234 }
235 235
236 236 h1:hover > a.permalink,
237 237 h2:hover > a.permalink,
238 238 h3:hover > a.permalink,
239 239 h4:hover > a.permalink,
240 240 h5:hover > a.permalink,
241 241 h6:hover > a.permalink,
242 242 div:hover > a.permalink {
243 243 visibility: visible;
244 244 }
245 245
246 246 #header {
247 247 margin: 0;
248 248 padding: 0 10px;
249 249 }
250 250
251 251 #header ul#logged-user {
252 252 margin-bottom: 5px !important;
253 253 -webkit-border-radius: 0px 0px 8px 8px;
254 254 -khtml-border-radius: 0px 0px 8px 8px;
255 255 -moz-border-radius: 0px 0px 8px 8px;
256 256 border-radius: 0px 0px 8px 8px;
257 257 height: 37px;
258 258 background-color: #003B76;
259 259 background-repeat: repeat-x;
260 260 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
261 261 background-image: -moz-linear-gradient(top, #003b76, #00376e);
262 262 background-image: -ms-linear-gradient(top, #003b76, #00376e);
263 263 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
264 264 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
265 265 background-image: -o-linear-gradient(top, #003b76, #00376e);
266 266 background-image: linear-gradient(top, #003b76, #00376e);
267 267 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
268 268 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
269 269 }
270 270
271 271 #header ul#logged-user li {
272 272 list-style: none;
273 273 float: left;
274 274 margin: 8px 0 0;
275 275 padding: 4px 12px;
276 276 border-left: 1px solid #316293;
277 277 }
278 278
279 279 #header ul#logged-user li.first {
280 280 border-left: none;
281 281 margin: 4px;
282 282 }
283 283
284 284 #header ul#logged-user li.first div.gravatar {
285 285 margin-top: -2px;
286 286 }
287 287
288 288 #header ul#logged-user li.first div.account {
289 289 padding-top: 4px;
290 290 float: left;
291 291 }
292 292
293 293 #header ul#logged-user li.last {
294 294 border-right: none;
295 295 }
296 296
297 297 #header ul#logged-user li a {
298 298 color: #fff;
299 299 font-weight: 700;
300 300 text-decoration: none;
301 301 }
302 302
303 303 #header ul#logged-user li a:hover {
304 304 text-decoration: underline;
305 305 }
306 306
307 307 #header ul#logged-user li.highlight a {
308 308 color: #fff;
309 309 }
310 310
311 311 #header ul#logged-user li.highlight a:hover {
312 312 color: #FFF;
313 313 }
314 314
315 315 #header #header-inner {
316 316 min-height: 44px;
317 317 clear: both;
318 318 position: relative;
319 319 background-color: #003B76;
320 320 background-repeat: repeat-x;
321 321 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
322 322 background-image: -moz-linear-gradient(top, #003b76, #00376e);
323 323 background-image: -ms-linear-gradient(top, #003b76, #00376e);
324 324 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
325 325 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
326 326 background-image: -o-linear-gradient(top, #003b76, #00376e);
327 327 background-image: linear-gradient(top, #003b76, #00376e);
328 328 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
329 329 margin: 0;
330 330 padding: 0;
331 331 display: block;
332 332 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
333 333 -webkit-border-radius: 4px 4px 4px 4px;
334 334 -khtml-border-radius: 4px 4px 4px 4px;
335 335 -moz-border-radius: 4px 4px 4px 4px;
336 336 border-radius: 4px 4px 4px 4px;
337 337 }
338 338 #header #header-inner.hover{
339 339 position: fixed !important;
340 340 width: 100% !important;
341 341 margin-left: -10px !important;
342 342 z-index: 10000;
343 343 -webkit-border-radius: 0px 0px 0px 0px;
344 344 -khtml-border-radius: 0px 0px 0px 0px;
345 345 -moz-border-radius: 0px 0px 0px 0px;
346 346 border-radius: 0px 0px 0px 0px;
347 347 }
348 348
349 349 .ie7 #header #header-inner.hover,
350 350 .ie8 #header #header-inner.hover,
351 351 .ie9 #header #header-inner.hover
352 352 {
353 353 z-index: auto !important;
354 354 }
355 355
356 356 .header-pos-fix, .anchor{
357 357 margin-top: -46px;
358 358 padding-top: 46px;
359 359 }
360 360
361 361 #header #header-inner #home a {
362 362 height: 40px;
363 363 width: 46px;
364 364 display: block;
365 365 background: url("../images/button_home.png");
366 366 background-position: 0 0;
367 367 margin: 0;
368 368 padding: 0;
369 369 }
370 370
371 371 #header #header-inner #home a:hover {
372 372 background-position: 0 -40px;
373 373 }
374 374
375 375 #header #header-inner #logo {
376 376 float: left;
377 377 position: absolute;
378 378 }
379 379
380 380 #header #header-inner #logo h1 {
381 381 color: #FFF;
382 382 font-size: 20px;
383 383 margin: 12px 0 0 13px;
384 384 padding: 0;
385 385 }
386 386
387 387 #header #header-inner #logo a {
388 388 color: #fff;
389 389 text-decoration: none;
390 390 }
391 391
392 392 #header #header-inner #logo a:hover {
393 393 color: #bfe3ff;
394 394 }
395 395
396 396 #header #header-inner #quick,#header #header-inner #quick ul {
397 397 position: relative;
398 398 float: right;
399 399 list-style-type: none;
400 400 list-style-position: outside;
401 401 margin: 8px 8px 0 0;
402 402 padding: 0;
403 403 }
404 404
405 405 #header #header-inner #quick li {
406 406 position: relative;
407 407 float: left;
408 408 margin: 0 5px 0 0;
409 409 padding: 0;
410 410 }
411 411
412 412 #header #header-inner #quick li a.menu_link {
413 413 top: 0;
414 414 left: 0;
415 415 height: 1%;
416 416 display: block;
417 417 clear: both;
418 418 overflow: hidden;
419 419 color: #FFF;
420 420 font-weight: 700;
421 421 text-decoration: none;
422 422 background: #369;
423 423 padding: 0;
424 424 -webkit-border-radius: 4px 4px 4px 4px;
425 425 -khtml-border-radius: 4px 4px 4px 4px;
426 426 -moz-border-radius: 4px 4px 4px 4px;
427 427 border-radius: 4px 4px 4px 4px;
428 428 }
429 429
430 430 #header #header-inner #quick li span.short {
431 431 padding: 9px 6px 8px 6px;
432 432 }
433 433
434 434 #header #header-inner #quick li span {
435 435 top: 0;
436 436 right: 0;
437 437 height: 1%;
438 438 display: block;
439 439 float: left;
440 440 border-left: 1px solid #3f6f9f;
441 441 margin: 0;
442 442 padding: 10px 12px 8px 10px;
443 443 }
444 444
445 445 #header #header-inner #quick li span.normal {
446 446 border: none;
447 447 padding: 10px 12px 8px;
448 448 }
449 449
450 450 #header #header-inner #quick li span.icon {
451 451 top: 0;
452 452 left: 0;
453 453 border-left: none;
454 454 border-right: 1px solid #2e5c89;
455 455 padding: 8px 6px 4px;
456 456 }
457 457
458 458 #header #header-inner #quick li span.icon_short {
459 459 top: 0;
460 460 left: 0;
461 461 border-left: none;
462 462 border-right: 1px solid #2e5c89;
463 463 padding: 8px 6px 4px;
464 464 }
465 465
466 466 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
467 467 {
468 468 margin: 0px -2px 0px 0px;
469 469 }
470 470
471 471 #header #header-inner #quick li a:hover {
472 472 background: #4e4e4e no-repeat top left;
473 473 }
474 474
475 475 #header #header-inner #quick li a:hover span {
476 476 border-left: 1px solid #545454;
477 477 }
478 478
479 479 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
480 480 {
481 481 border-left: none;
482 482 border-right: 1px solid #464646;
483 483 }
484 484
485 485 #header #header-inner #quick ul {
486 486 top: 29px;
487 487 right: 0;
488 488 min-width: 200px;
489 489 display: none;
490 490 position: absolute;
491 491 background: #FFF;
492 492 border: 1px solid #666;
493 493 border-top: 1px solid #003367;
494 494 z-index: 100;
495 495 margin: 0px 0px 0px 0px;
496 496 padding: 0;
497 497 }
498 498
499 499 #header #header-inner #quick ul.repo_switcher {
500 500 max-height: 275px;
501 501 overflow-x: hidden;
502 502 overflow-y: auto;
503 503 }
504 504
505 505 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
506 506 float: none;
507 507 margin: 0;
508 508 border-bottom: 2px solid #003367;
509 509 }
510 510
511 511 #header #header-inner #quick .repo_switcher_type {
512 512 position: absolute;
513 513 left: 0;
514 514 top: 9px;
515 515 }
516 516
517 517 #header #header-inner #quick li ul li {
518 518 border-bottom: 1px solid #ddd;
519 519 }
520 520
521 521 #header #header-inner #quick li ul li a {
522 522 width: 182px;
523 523 height: auto;
524 524 display: block;
525 525 float: left;
526 526 background: #FFF;
527 527 color: #003367;
528 528 font-weight: 400;
529 529 margin: 0;
530 530 padding: 7px 9px;
531 531 }
532 532
533 533 #header #header-inner #quick li ul li a:hover {
534 534 color: #000;
535 535 background: #FFF;
536 536 }
537 537
538 538 #header #header-inner #quick ul ul {
539 539 top: auto;
540 540 }
541 541
542 542 #header #header-inner #quick li ul ul {
543 543 right: 200px;
544 544 max-height: 290px;
545 545 overflow: auto;
546 546 overflow-x: hidden;
547 547 white-space: normal;
548 548 }
549 549
550 550 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
551 551 {
552 552 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
553 553 #FFF;
554 554 width: 167px;
555 555 margin: 0;
556 556 padding: 12px 9px 7px 24px;
557 557 }
558 558
559 559 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
560 560 {
561 561 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
562 562 #FFF;
563 563 min-width: 167px;
564 564 margin: 0;
565 565 padding: 12px 9px 7px 24px;
566 566 }
567 567
568 568 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
569 569 {
570 570 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
571 571 9px #FFF;
572 572 min-width: 167px;
573 573 margin: 0;
574 574 padding: 12px 9px 7px 24px;
575 575 }
576 576
577 577 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
578 578 {
579 579 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
580 580 #FFF;
581 581 min-width: 167px;
582 582 margin: 0 0 0 14px;
583 583 padding: 12px 9px 7px 24px;
584 584 }
585 585
586 586 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
587 587 {
588 588 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
589 589 #FFF;
590 590 min-width: 167px;
591 591 margin: 0 0 0 14px;
592 592 padding: 12px 9px 7px 24px;
593 593 }
594 594
595 595 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
596 596 {
597 597 background: url("../images/icons/database_edit.png") no-repeat scroll
598 598 4px 9px #FFF;
599 599 width: 167px;
600 600 margin: 0;
601 601 padding: 12px 9px 7px 24px;
602 602 }
603 603
604 604 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
605 605 {
606 606 background: url("../images/icons/database_link.png") no-repeat scroll
607 607 4px 9px #FFF;
608 608 width: 167px;
609 609 margin: 0;
610 610 padding: 12px 9px 7px 24px;
611 611 }
612 612
613 613 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
614 614 {
615 615 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
616 616 width: 167px;
617 617 margin: 0;
618 618 padding: 12px 9px 7px 24px;
619 619 }
620 620
621 621 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
622 622 {
623 623 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
624 624 width: 167px;
625 625 margin: 0;
626 626 padding: 12px 9px 7px 24px;
627 627 }
628 628
629 629 #header #header-inner #quick li ul li a.defaults,#header #header-inner #quick li ul li a.defaults:hover
630 630 {
631 631 background: #FFF url("../images/icons/wrench.png") no-repeat 4px 9px;
632 632 width: 167px;
633 633 margin: 0;
634 634 padding: 12px 9px 7px 24px;
635 635 }
636 636
637 637 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
638 638 {
639 639 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
640 640 width: 167px;
641 641 margin: 0;
642 642 padding: 12px 9px 7px 24px;
643 643 }
644 644
645 645 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
646 646 {
647 647 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
648 648 width: 167px;
649 649 margin: 0;
650 650 padding: 12px 9px 7px 24px;
651 651 }
652 652
653 653 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
654 654 {
655 655 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
656 656 width: 167px;
657 657 margin: 0;
658 658 padding: 12px 9px 7px 24px;
659 659 }
660 660
661 661 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
662 662 {
663 663 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
664 664 9px;
665 665 width: 167px;
666 666 margin: 0;
667 667 padding: 12px 9px 7px 24px;
668 668 }
669 669
670 670 #header #header-inner #quick li ul li a.locking_add,#header #header-inner #quick li ul li a.locking_add:hover
671 671 {
672 672 background: #FFF url("../images/icons/lock_add.png") no-repeat 4px
673 673 9px;
674 674 width: 167px;
675 675 margin: 0;
676 676 padding: 12px 9px 7px 24px;
677 677 }
678 678
679 679 #header #header-inner #quick li ul li a.locking_del,#header #header-inner #quick li ul li a.locking_del:hover
680 680 {
681 681 background: #FFF url("../images/icons/lock_delete.png") no-repeat 4px
682 682 9px;
683 683 width: 167px;
684 684 margin: 0;
685 685 padding: 12px 9px 7px 24px;
686 686 }
687 687
688 688 #header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover
689 689 {
690 690 background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px
691 691 9px;
692 692 width: 167px;
693 693 margin: 0;
694 694 padding: 12px 9px 7px 24px;
695 695 }
696 696
697 697 #header #header-inner #quick li ul li a.compare_request,#header #header-inner #quick li ul li a.compare_request:hover
698 698 {
699 699 background: #FFF url("../images/icons/arrow_inout.png") no-repeat 4px
700 700 9px;
701 701 width: 167px;
702 702 margin: 0;
703 703 padding: 12px 9px 7px 24px;
704 704 }
705 705
706 706 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
707 707 {
708 708 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
709 709 width: 167px;
710 710 margin: 0;
711 711 padding: 12px 9px 7px 24px;
712 712 }
713 713
714 714 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
715 715 {
716 716 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
717 717 width: 167px;
718 718 margin: 0;
719 719 padding: 12px 9px 7px 24px;
720 720 }
721 721
722 722 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
723 723 {
724 724 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
725 725 9px;
726 726 width: 167px;
727 727 margin: 0;
728 728 padding: 12px 9px 7px 24px;
729 729 }
730 730
731 731 #header #header-inner #quick li ul li a.tags,
732 732 #header #header-inner #quick li ul li a.tags:hover{
733 733 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
734 734 width: 167px;
735 735 margin: 0;
736 736 padding: 12px 9px 7px 24px;
737 737 }
738 738
739 739 #header #header-inner #quick li ul li a.bookmarks,
740 740 #header #header-inner #quick li ul li a.bookmarks:hover{
741 741 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
742 742 width: 167px;
743 743 margin: 0;
744 744 padding: 12px 9px 7px 24px;
745 745 }
746 746
747 747 #header #header-inner #quick li ul li a.admin,
748 748 #header #header-inner #quick li ul li a.admin:hover{
749 749 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
750 750 width: 167px;
751 751 margin: 0;
752 752 padding: 12px 9px 7px 24px;
753 753 }
754 754
755 755 .groups_breadcrumbs a {
756 756 color: #fff;
757 757 }
758 758
759 759 .groups_breadcrumbs a:hover {
760 760 color: #bfe3ff;
761 761 text-decoration: none;
762 762 }
763 763
764 764 td.quick_repo_menu {
765 765 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
766 766 cursor: pointer;
767 767 width: 8px;
768 768 border: 1px solid transparent;
769 769 }
770 770
771 771 td.quick_repo_menu.active {
772 772 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
773 773 border: 1px solid #003367;
774 774 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
775 775 cursor: pointer;
776 776 }
777 777
778 778 td.quick_repo_menu .menu_items {
779 779 margin-top: 10px;
780 780 margin-left:-6px;
781 781 width: 150px;
782 782 position: absolute;
783 783 background-color: #FFF;
784 784 background: none repeat scroll 0 0 #FFFFFF;
785 785 border-color: #003367 #666666 #666666;
786 786 border-right: 1px solid #666666;
787 787 border-style: solid;
788 788 border-width: 1px;
789 789 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
790 790 border-top-style: none;
791 791 }
792 792
793 793 td.quick_repo_menu .menu_items li {
794 794 padding: 0 !important;
795 795 }
796 796
797 797 td.quick_repo_menu .menu_items a {
798 798 display: block;
799 799 padding: 4px 12px 4px 8px;
800 800 }
801 801
802 802 td.quick_repo_menu .menu_items a:hover {
803 803 background-color: #EEE;
804 804 text-decoration: none;
805 805 }
806 806
807 807 td.quick_repo_menu .menu_items .icon img {
808 808 margin-bottom: -2px;
809 809 }
810 810
811 811 td.quick_repo_menu .menu_items.hidden {
812 812 display: none;
813 813 }
814 814
815 815 .yui-dt-first th {
816 816 text-align: left;
817 817 }
818 818
819 819 /*
820 820 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
821 821 Code licensed under the BSD License:
822 822 http://developer.yahoo.com/yui/license.html
823 823 version: 2.9.0
824 824 */
825 825 .yui-skin-sam .yui-dt-mask {
826 826 position: absolute;
827 827 z-index: 9500;
828 828 }
829 829 .yui-dt-tmp {
830 830 position: absolute;
831 831 left: -9000px;
832 832 }
833 833 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
834 834 .yui-dt-scrollable .yui-dt-hd {
835 835 overflow: hidden;
836 836 position: relative;
837 837 }
838 838 .yui-dt-scrollable .yui-dt-bd thead tr,
839 839 .yui-dt-scrollable .yui-dt-bd thead th {
840 840 position: absolute;
841 841 left: -1500px;
842 842 }
843 843 .yui-dt-scrollable tbody { -moz-outline: 0 }
844 844 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
845 845 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
846 846 .yui-dt-coltarget {
847 847 position: absolute;
848 848 z-index: 999;
849 849 }
850 850 .yui-dt-hd { zoom: 1 }
851 851 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
852 852 .yui-dt-resizer {
853 853 position: absolute;
854 854 right: 0;
855 855 bottom: 0;
856 856 height: 100%;
857 857 cursor: e-resize;
858 858 cursor: col-resize;
859 859 background-color: #CCC;
860 860 opacity: 0;
861 861 filter: alpha(opacity=0);
862 862 }
863 863 .yui-dt-resizerproxy {
864 864 visibility: hidden;
865 865 position: absolute;
866 866 z-index: 9000;
867 867 background-color: #CCC;
868 868 opacity: 0;
869 869 filter: alpha(opacity=0);
870 870 }
871 871 th.yui-dt-hidden .yui-dt-liner,
872 872 td.yui-dt-hidden .yui-dt-liner,
873 873 th.yui-dt-hidden .yui-dt-resizer { display: none }
874 874 .yui-dt-editor,
875 875 .yui-dt-editor-shim {
876 876 position: absolute;
877 877 z-index: 9000;
878 878 }
879 879 .yui-skin-sam .yui-dt table {
880 880 margin: 0;
881 881 padding: 0;
882 882 font-family: arial;
883 883 font-size: inherit;
884 884 border-collapse: separate;
885 885 *border-collapse: collapse;
886 886 border-spacing: 0;
887 887 border: 1px solid #7f7f7f;
888 888 }
889 889 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
890 890 .yui-skin-sam .yui-dt caption {
891 891 color: #000;
892 892 font-size: 85%;
893 893 font-weight: normal;
894 894 font-style: italic;
895 895 line-height: 1;
896 896 padding: 1em 0;
897 897 text-align: center;
898 898 }
899 899 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
900 900 .yui-skin-sam .yui-dt th,
901 901 .yui-skin-sam .yui-dt th a {
902 902 font-weight: normal;
903 903 text-decoration: none;
904 904 color: #000;
905 905 vertical-align: bottom;
906 906 }
907 907 .yui-skin-sam .yui-dt th {
908 908 margin: 0;
909 909 padding: 0;
910 910 border: 0;
911 911 border-right: 1px solid #cbcbcb;
912 912 }
913 913 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
914 914 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
915 915 .yui-skin-sam .yui-dt-liner {
916 916 margin: 0;
917 917 padding: 0;
918 918 }
919 919 .yui-skin-sam .yui-dt-coltarget {
920 920 width: 5px;
921 921 background-color: red;
922 922 }
923 923 .yui-skin-sam .yui-dt td {
924 924 margin: 0;
925 925 padding: 0;
926 926 border: 0;
927 927 border-right: 1px solid #cbcbcb;
928 928 text-align: left;
929 929 }
930 930 .yui-skin-sam .yui-dt-list td { border-right: 0 }
931 931 .yui-skin-sam .yui-dt-resizer { width: 6px }
932 932 .yui-skin-sam .yui-dt-mask {
933 933 background-color: #000;
934 934 opacity: .25;
935 935 filter: alpha(opacity=25);
936 936 }
937 937 .yui-skin-sam .yui-dt-message { background-color: #FFF }
938 938 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
939 939 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
940 940 border-left: 1px solid #7f7f7f;
941 941 border-top: 1px solid #7f7f7f;
942 942 border-right: 1px solid #7f7f7f;
943 943 }
944 944 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
945 945 border-left: 1px solid #7f7f7f;
946 946 border-bottom: 1px solid #7f7f7f;
947 947 border-right: 1px solid #7f7f7f;
948 948 background-color: #FFF;
949 949 }
950 950 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
951 951 .yui-skin-sam th.yui-dt-asc,
952 952 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
953 953 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
954 954 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
955 955 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
956 956 tbody .yui-dt-editable { cursor: pointer }
957 957 .yui-dt-editor {
958 958 text-align: left;
959 959 background-color: #f2f2f2;
960 960 border: 1px solid #808080;
961 961 padding: 6px;
962 962 }
963 963 .yui-dt-editor label {
964 964 padding-left: 4px;
965 965 padding-right: 6px;
966 966 }
967 967 .yui-dt-editor .yui-dt-button {
968 968 padding-top: 6px;
969 969 text-align: right;
970 970 }
971 971 .yui-dt-editor .yui-dt-button button {
972 972 background: url(../images/sprite.png) repeat-x 0 0;
973 973 border: 1px solid #999;
974 974 width: 4em;
975 975 height: 1.8em;
976 976 margin-left: 6px;
977 977 }
978 978 .yui-dt-editor .yui-dt-button button.yui-dt-default {
979 979 background: url(../images/sprite.png) repeat-x 0 -1400px;
980 980 background-color: #5584e0;
981 981 border: 1px solid #304369;
982 982 color: #FFF;
983 983 }
984 984 .yui-dt-editor .yui-dt-button button:hover {
985 985 background: url(../images/sprite.png) repeat-x 0 -1300px;
986 986 color: #000;
987 987 }
988 988 .yui-dt-editor .yui-dt-button button:active {
989 989 background: url(../images/sprite.png) repeat-x 0 -1700px;
990 990 color: #000;
991 991 }
992 992 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
993 993 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
994 994 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
995 995 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
996 996 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
997 997 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
998 998 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
999 999 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
1000 1000 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
1001 1001 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
1002 1002 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
1003 1003 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
1004 1004 .yui-skin-sam th.yui-dt-highlighted,
1005 1005 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
1006 1006 .yui-skin-sam tr.yui-dt-highlighted,
1007 1007 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
1008 1008 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
1009 1009 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
1010 1010 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
1011 1011 cursor: pointer;
1012 1012 background-color: #b2d2ff;
1013 1013 }
1014 1014 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
1015 1015 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
1016 1016 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
1017 1017 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
1018 1018 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
1019 1019 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
1020 1020 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
1021 1021 cursor: pointer;
1022 1022 background-color: #b2d2ff;
1023 1023 }
1024 1024 .yui-skin-sam th.yui-dt-selected,
1025 1025 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
1026 1026 .yui-skin-sam tr.yui-dt-selected td,
1027 1027 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
1028 1028 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
1029 1029 background-color: #426fd9;
1030 1030 color: #FFF;
1031 1031 }
1032 1032 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
1033 1033 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
1034 1034 background-color: #446cd7;
1035 1035 color: #FFF;
1036 1036 }
1037 1037 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
1038 1038 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
1039 1039 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
1040 1040 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
1041 1041 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
1042 1042 background-color: #426fd9;
1043 1043 color: #FFF;
1044 1044 }
1045 1045 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
1046 1046 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
1047 1047 background-color: #446cd7;
1048 1048 color: #FFF;
1049 1049 }
1050 1050 .yui-skin-sam .yui-dt-paginator {
1051 1051 display: block;
1052 1052 margin: 6px 0;
1053 1053 white-space: nowrap;
1054 1054 }
1055 1055 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
1056 1056 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
1057 1057 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
1058 1058 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
1059 1059 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
1060 1060 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
1061 1061 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
1062 1062 .yui-skin-sam a.yui-dt-page {
1063 1063 border: 1px solid #cbcbcb;
1064 1064 padding: 2px 6px;
1065 1065 text-decoration: none;
1066 1066 background-color: #fff;
1067 1067 }
1068 1068 .yui-skin-sam .yui-dt-selected {
1069 1069 border: 1px solid #fff;
1070 1070 background-color: #fff;
1071 1071 }
1072 1072
1073 1073 #content #left {
1074 1074 left: 0;
1075 1075 width: 280px;
1076 1076 position: absolute;
1077 1077 }
1078 1078
1079 1079 #content #right {
1080 1080 margin: 0 60px 10px 290px;
1081 1081 }
1082 1082
1083 1083 #content div.box {
1084 1084 clear: both;
1085 1085 overflow: hidden;
1086 1086 background: #fff;
1087 1087 margin: 0 0 10px;
1088 1088 padding: 0 0 10px;
1089 1089 -webkit-border-radius: 4px 4px 4px 4px;
1090 1090 -khtml-border-radius: 4px 4px 4px 4px;
1091 1091 -moz-border-radius: 4px 4px 4px 4px;
1092 1092 border-radius: 4px 4px 4px 4px;
1093 1093 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1094 1094 }
1095 1095
1096 1096 #content div.box-left {
1097 1097 width: 49%;
1098 1098 clear: none;
1099 1099 float: left;
1100 1100 margin: 0 0 10px;
1101 1101 }
1102 1102
1103 1103 #content div.box-right {
1104 1104 width: 49%;
1105 1105 clear: none;
1106 1106 float: right;
1107 1107 margin: 0 0 10px;
1108 1108 }
1109 1109
1110 1110 #content div.box div.title {
1111 1111 clear: both;
1112 1112 overflow: hidden;
1113 1113 background-color: #003B76;
1114 1114 background-repeat: repeat-x;
1115 1115 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1116 1116 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1117 1117 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1118 1118 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1119 1119 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1120 1120 background-image: -o-linear-gradient(top, #003b76, #00376e);
1121 1121 background-image: linear-gradient(top, #003b76, #00376e);
1122 1122 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1123 1123 margin: 0 0 20px;
1124 1124 padding: 0;
1125 1125 }
1126 1126
1127 1127 #content div.box div.title h5 {
1128 1128 float: left;
1129 1129 border: none;
1130 1130 color: #fff;
1131 1131 text-transform: uppercase;
1132 1132 margin: 0;
1133 1133 padding: 11px 0 11px 10px;
1134 1134 }
1135 1135
1136 1136 #content div.box div.title .link-white{
1137 1137 color: #FFFFFF;
1138 1138 }
1139 1139
1140 1140 #content div.box div.title .link-white.current{
1141 1141 color: #BFE3FF;
1142 1142 }
1143 1143
1144 1144 #content div.box div.title ul.links li {
1145 1145 list-style: none;
1146 1146 float: left;
1147 1147 margin: 0;
1148 1148 padding: 0;
1149 1149 }
1150 1150
1151 1151 #content div.box div.title ul.links li a {
1152 1152 border-left: 1px solid #316293;
1153 1153 color: #FFFFFF;
1154 1154 display: block;
1155 1155 float: left;
1156 1156 font-size: 13px;
1157 1157 font-weight: 700;
1158 1158 height: 1%;
1159 1159 margin: 0;
1160 1160 padding: 11px 22px 12px;
1161 1161 text-decoration: none;
1162 1162 }
1163 1163
1164 1164 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6,
1165 1165 #content div.box div.h1,#content div.box div.h2,#content div.box div.h3,#content div.box div.h4,#content div.box div.h5,#content div.box div.h6
1166 1166
1167 1167 {
1168 1168 clear: both;
1169 1169 overflow: hidden;
1170 1170 border-bottom: 1px solid #DDD;
1171 1171 margin: 10px 20px;
1172 1172 padding: 0 0 15px;
1173 1173 }
1174 1174
1175 1175 #content div.box p {
1176 1176 color: #5f5f5f;
1177 1177 font-size: 12px;
1178 1178 line-height: 150%;
1179 1179 margin: 0 24px 10px;
1180 1180 padding: 0;
1181 1181 }
1182 1182
1183 1183 #content div.box blockquote {
1184 1184 border-left: 4px solid #DDD;
1185 1185 color: #5f5f5f;
1186 1186 font-size: 11px;
1187 1187 line-height: 150%;
1188 1188 margin: 0 34px;
1189 1189 padding: 0 0 0 14px;
1190 1190 }
1191 1191
1192 1192 #content div.box blockquote p {
1193 1193 margin: 10px 0;
1194 1194 padding: 0;
1195 1195 }
1196 1196
1197 1197 #content div.box dl {
1198 1198 margin: 10px 0px;
1199 1199 }
1200 1200
1201 1201 #content div.box dt {
1202 1202 font-size: 12px;
1203 1203 margin: 0;
1204 1204 }
1205 1205
1206 1206 #content div.box dd {
1207 1207 font-size: 12px;
1208 1208 margin: 0;
1209 1209 padding: 8px 0 8px 15px;
1210 1210 }
1211 1211
1212 1212 #content div.box li {
1213 1213 font-size: 12px;
1214 1214 padding: 4px 0;
1215 1215 }
1216 1216
1217 1217 #content div.box ul.disc,#content div.box ul.circle {
1218 1218 margin: 10px 24px 10px 38px;
1219 1219 }
1220 1220
1221 1221 #content div.box ul.square {
1222 1222 margin: 10px 24px 10px 40px;
1223 1223 }
1224 1224
1225 1225 #content div.box img.left {
1226 1226 border: none;
1227 1227 float: left;
1228 1228 margin: 10px 10px 10px 0;
1229 1229 }
1230 1230
1231 1231 #content div.box img.right {
1232 1232 border: none;
1233 1233 float: right;
1234 1234 margin: 10px 0 10px 10px;
1235 1235 }
1236 1236
1237 1237 #content div.box div.messages {
1238 1238 clear: both;
1239 1239 overflow: hidden;
1240 1240 margin: 0 20px;
1241 1241 padding: 0;
1242 1242 }
1243 1243
1244 1244 #content div.box div.message {
1245 1245 clear: both;
1246 1246 overflow: hidden;
1247 1247 margin: 0;
1248 1248 padding: 5px 0;
1249 1249 white-space: pre-wrap;
1250 1250 }
1251 1251 #content div.box div.expand {
1252 1252 width: 110%;
1253 1253 height:14px;
1254 1254 font-size:10px;
1255 1255 text-align:center;
1256 1256 cursor: pointer;
1257 1257 color:#666;
1258 1258
1259 1259 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1260 1260 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1261 1261 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1262 1262 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1263 1263 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1264 1264 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1265 1265
1266 1266 display: none;
1267 1267 }
1268 1268 #content div.box div.expand .expandtext {
1269 1269 background-color: #ffffff;
1270 1270 padding: 2px;
1271 1271 border-radius: 2px;
1272 1272 }
1273 1273
1274 1274 #content div.box div.message a {
1275 1275 font-weight: 400 !important;
1276 1276 }
1277 1277
1278 1278 #content div.box div.message div.image {
1279 1279 float: left;
1280 1280 margin: 9px 0 0 5px;
1281 1281 padding: 6px;
1282 1282 }
1283 1283
1284 1284 #content div.box div.message div.image img {
1285 1285 vertical-align: middle;
1286 1286 margin: 0;
1287 1287 }
1288 1288
1289 1289 #content div.box div.message div.text {
1290 1290 float: left;
1291 1291 margin: 0;
1292 1292 padding: 9px 6px;
1293 1293 }
1294 1294
1295 1295 #content div.box div.message div.dismiss a {
1296 1296 height: 16px;
1297 1297 width: 16px;
1298 1298 display: block;
1299 1299 background: url("../images/icons/cross.png") no-repeat;
1300 1300 margin: 15px 14px 0 0;
1301 1301 padding: 0;
1302 1302 }
1303 1303
1304 1304 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
1305 1305 {
1306 1306 border: none;
1307 1307 margin: 0;
1308 1308 padding: 0;
1309 1309 }
1310 1310
1311 1311 #content div.box div.message div.text span {
1312 1312 height: 1%;
1313 1313 display: block;
1314 1314 margin: 0;
1315 1315 padding: 5px 0 0;
1316 1316 }
1317 1317
1318 1318 #content div.box div.message-error {
1319 1319 height: 1%;
1320 1320 clear: both;
1321 1321 overflow: hidden;
1322 1322 background: #FBE3E4;
1323 1323 border: 1px solid #FBC2C4;
1324 1324 color: #860006;
1325 1325 }
1326 1326
1327 1327 #content div.box div.message-error h6 {
1328 1328 color: #860006;
1329 1329 }
1330 1330
1331 1331 #content div.box div.message-warning {
1332 1332 height: 1%;
1333 1333 clear: both;
1334 1334 overflow: hidden;
1335 1335 background: #FFF6BF;
1336 1336 border: 1px solid #FFD324;
1337 1337 color: #5f5200;
1338 1338 }
1339 1339
1340 1340 #content div.box div.message-warning h6 {
1341 1341 color: #5f5200;
1342 1342 }
1343 1343
1344 1344 #content div.box div.message-notice {
1345 1345 height: 1%;
1346 1346 clear: both;
1347 1347 overflow: hidden;
1348 1348 background: #8FBDE0;
1349 1349 border: 1px solid #6BACDE;
1350 1350 color: #003863;
1351 1351 }
1352 1352
1353 1353 #content div.box div.message-notice h6 {
1354 1354 color: #003863;
1355 1355 }
1356 1356
1357 1357 #content div.box div.message-success {
1358 1358 height: 1%;
1359 1359 clear: both;
1360 1360 overflow: hidden;
1361 1361 background: #E6EFC2;
1362 1362 border: 1px solid #C6D880;
1363 1363 color: #4e6100;
1364 1364 }
1365 1365
1366 1366 #content div.box div.message-success h6 {
1367 1367 color: #4e6100;
1368 1368 }
1369 1369
1370 1370 #content div.box div.form div.fields div.field {
1371 1371 height: 1%;
1372 1372 border-bottom: 1px solid #DDD;
1373 1373 clear: both;
1374 1374 margin: 0;
1375 1375 padding: 10px 0;
1376 1376 }
1377 1377
1378 1378 #content div.box div.form div.fields div.field-first {
1379 1379 padding: 0 0 10px;
1380 1380 }
1381 1381
1382 1382 #content div.box div.form div.fields div.field-noborder {
1383 1383 border-bottom: 0 !important;
1384 1384 }
1385 1385
1386 1386 #content div.box div.form div.fields div.field span.error-message {
1387 1387 height: 1%;
1388 1388 display: inline-block;
1389 1389 color: red;
1390 1390 margin: 8px 0 0 4px;
1391 1391 padding: 0;
1392 1392 }
1393 1393
1394 1394 #content div.box div.form div.fields div.field span.success {
1395 1395 height: 1%;
1396 1396 display: block;
1397 1397 color: #316309;
1398 1398 margin: 8px 0 0;
1399 1399 padding: 0;
1400 1400 }
1401 1401
1402 1402 #content div.box div.form div.fields div.field div.label {
1403 1403 left: 70px;
1404 1404 width: 155px;
1405 1405 position: absolute;
1406 1406 margin: 0;
1407 1407 padding: 5px 0 0 0px;
1408 1408 }
1409 1409
1410 1410 #content div.box div.form div.fields div.field div.label-summary {
1411 1411 left: 30px;
1412 1412 width: 155px;
1413 1413 position: absolute;
1414 1414 margin: 0;
1415 1415 padding: 0px 0 0 0px;
1416 1416 }
1417 1417
1418 1418 #content div.box-left div.form div.fields div.field div.label,
1419 1419 #content div.box-right div.form div.fields div.field div.label,
1420 1420 #content div.box-left div.form div.fields div.field div.label,
1421 1421 #content div.box-left div.form div.fields div.field div.label-summary,
1422 1422 #content div.box-right div.form div.fields div.field div.label-summary,
1423 1423 #content div.box-left div.form div.fields div.field div.label-summary
1424 1424 {
1425 1425 clear: both;
1426 1426 overflow: hidden;
1427 1427 left: 0;
1428 1428 width: auto;
1429 1429 position: relative;
1430 1430 margin: 0;
1431 1431 padding: 0 0 8px;
1432 1432 }
1433 1433
1434 1434 #content div.box div.form div.fields div.field div.label-select {
1435 1435 padding: 5px 0 0 5px;
1436 1436 }
1437 1437
1438 1438 #content div.box-left div.form div.fields div.field div.label-select,
1439 1439 #content div.box-right div.form div.fields div.field div.label-select
1440 1440 {
1441 1441 padding: 0 0 8px;
1442 1442 }
1443 1443
1444 1444 #content div.box-left div.form div.fields div.field div.label-textarea,
1445 1445 #content div.box-right div.form div.fields div.field div.label-textarea
1446 1446 {
1447 1447 padding: 0 0 8px !important;
1448 1448 }
1449 1449
1450 1450 #content div.box div.form div.fields div.field div.label label,div.label label
1451 1451 {
1452 1452 color: #393939;
1453 1453 font-weight: 700;
1454 1454 }
1455 1455 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1456 1456 {
1457 1457 color: #393939;
1458 1458 font-weight: 700;
1459 1459 }
1460 1460 #content div.box div.form div.fields div.field div.input {
1461 1461 margin: 0 0 0 200px;
1462 1462 }
1463 1463
1464 1464 #content div.box div.form div.fields div.field div.input.summary {
1465 1465 margin: 0 0 0 110px;
1466 1466 }
1467 1467 #content div.box div.form div.fields div.field div.input.summary-short {
1468 1468 margin: 0 0 0 110px;
1469 1469 }
1470 1470 #content div.box div.form div.fields div.field div.file {
1471 1471 margin: 0 0 0 200px;
1472 1472 }
1473 1473
1474 1474 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1475 1475 {
1476 1476 margin: 0 0 0 0px;
1477 1477 }
1478 1478
1479 1479 #content div.box div.form div.fields div.field div.input input,
1480 1480 .reviewer_ac input {
1481 1481 background: #FFF;
1482 1482 border-top: 1px solid #b3b3b3;
1483 1483 border-left: 1px solid #b3b3b3;
1484 1484 border-right: 1px solid #eaeaea;
1485 1485 border-bottom: 1px solid #eaeaea;
1486 1486 color: #000;
1487 1487 font-size: 11px;
1488 1488 margin: 0;
1489 1489 padding: 7px 7px 6px;
1490 1490 }
1491 1491
1492 1492 #content div.box div.form div.fields div.field div.input input#clone_url,
1493 1493 #content div.box div.form div.fields div.field div.input input#clone_url_id
1494 1494 {
1495 1495 font-size: 16px;
1496 1496 padding: 2px;
1497 1497 }
1498 1498
1499 1499 #content div.box div.form div.fields div.field div.file input {
1500 1500 background: none repeat scroll 0 0 #FFFFFF;
1501 1501 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1502 1502 border-style: solid;
1503 1503 border-width: 1px;
1504 1504 color: #000000;
1505 1505 font-size: 11px;
1506 1506 margin: 0;
1507 1507 padding: 7px 7px 6px;
1508 1508 }
1509 1509
1510 1510 input.disabled {
1511 1511 background-color: #F5F5F5 !important;
1512 1512 }
1513 1513 #content div.box div.form div.fields div.field div.input input.small {
1514 1514 width: 30%;
1515 1515 }
1516 1516
1517 1517 #content div.box div.form div.fields div.field div.input input.medium {
1518 1518 width: 55%;
1519 1519 }
1520 1520
1521 1521 #content div.box div.form div.fields div.field div.input input.large {
1522 1522 width: 85%;
1523 1523 }
1524 1524
1525 1525 #content div.box div.form div.fields div.field div.input input.date {
1526 1526 width: 177px;
1527 1527 }
1528 1528
1529 1529 #content div.box div.form div.fields div.field div.input input.button {
1530 1530 background: #D4D0C8;
1531 1531 border-top: 1px solid #FFF;
1532 1532 border-left: 1px solid #FFF;
1533 1533 border-right: 1px solid #404040;
1534 1534 border-bottom: 1px solid #404040;
1535 1535 color: #000;
1536 1536 margin: 0;
1537 1537 padding: 4px 8px;
1538 1538 }
1539 1539
1540 1540 #content div.box div.form div.fields div.field div.textarea {
1541 1541 border-top: 1px solid #b3b3b3;
1542 1542 border-left: 1px solid #b3b3b3;
1543 1543 border-right: 1px solid #eaeaea;
1544 1544 border-bottom: 1px solid #eaeaea;
1545 1545 margin: 0 0 0 200px;
1546 1546 padding: 10px;
1547 1547 }
1548 1548
1549 1549 #content div.box div.form div.fields div.field div.textarea-editor {
1550 1550 border: 1px solid #ddd;
1551 1551 padding: 0;
1552 1552 }
1553 1553
1554 1554 #content div.box div.form div.fields div.field div.textarea textarea {
1555 1555 width: 100%;
1556 1556 height: 220px;
1557 1557 overflow: hidden;
1558 1558 background: #FFF;
1559 1559 color: #000;
1560 1560 font-size: 11px;
1561 1561 outline: none;
1562 1562 border-width: 0;
1563 1563 margin: 0;
1564 1564 padding: 0;
1565 1565 }
1566 1566
1567 1567 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1568 1568 {
1569 1569 width: 100%;
1570 1570 height: 100px;
1571 1571 }
1572 1572
1573 1573 #content div.box div.form div.fields div.field div.textarea table {
1574 1574 width: 100%;
1575 1575 border: none;
1576 1576 margin: 0;
1577 1577 padding: 0;
1578 1578 }
1579 1579
1580 1580 #content div.box div.form div.fields div.field div.textarea table td {
1581 1581 background: #DDD;
1582 1582 border: none;
1583 1583 padding: 0;
1584 1584 }
1585 1585
1586 1586 #content div.box div.form div.fields div.field div.textarea table td table
1587 1587 {
1588 1588 width: auto;
1589 1589 border: none;
1590 1590 margin: 0;
1591 1591 padding: 0;
1592 1592 }
1593 1593
1594 1594 #content div.box div.form div.fields div.field div.textarea table td table td
1595 1595 {
1596 1596 font-size: 11px;
1597 1597 padding: 5px 5px 5px 0;
1598 1598 }
1599 1599
1600 1600 #content div.box div.form div.fields div.field input[type=text]:focus,
1601 1601 #content div.box div.form div.fields div.field input[type=password]:focus,
1602 1602 #content div.box div.form div.fields div.field input[type=file]:focus,
1603 1603 #content div.box div.form div.fields div.field textarea:focus,
1604 1604 #content div.box div.form div.fields div.field select:focus,
1605 1605 .reviewer_ac input:focus
1606 1606 {
1607 1607 background: #f6f6f6;
1608 1608 border-color: #666;
1609 1609 }
1610 1610
1611 1611 .reviewer_ac {
1612 1612 padding:10px
1613 1613 }
1614 1614
1615 1615 div.form div.fields div.field div.button {
1616 1616 margin: 0;
1617 1617 padding: 0 0 0 8px;
1618 1618 }
1619 1619 #content div.box table.noborder {
1620 1620 border: 1px solid transparent;
1621 1621 }
1622 1622
1623 1623 #content div.box table {
1624 1624 width: 100%;
1625 1625 border-collapse: separate;
1626 1626 margin: 0;
1627 1627 padding: 0;
1628 1628 border: 1px solid #eee;
1629 1629 -webkit-border-radius: 4px;
1630 1630 -moz-border-radius: 4px;
1631 1631 border-radius: 4px;
1632 1632 }
1633 1633
1634 1634 #content div.box table th {
1635 1635 background: #eee;
1636 1636 border-bottom: 1px solid #ddd;
1637 1637 padding: 5px 0px 5px 5px;
1638 1638 text-align: left;
1639 1639 }
1640 1640
1641 1641 #content div.box table th.left {
1642 1642 text-align: left;
1643 1643 }
1644 1644
1645 1645 #content div.box table th.right {
1646 1646 text-align: right;
1647 1647 }
1648 1648
1649 1649 #content div.box table th.center {
1650 1650 text-align: center;
1651 1651 }
1652 1652
1653 1653 #content div.box table th.selected {
1654 1654 vertical-align: middle;
1655 1655 padding: 0;
1656 1656 }
1657 1657
1658 1658 #content div.box table td {
1659 1659 background: #fff;
1660 1660 border-bottom: 1px solid #cdcdcd;
1661 1661 vertical-align: middle;
1662 1662 padding: 5px;
1663 1663 }
1664 1664
1665 1665 #content div.box table tr.selected td {
1666 1666 background: #FFC;
1667 1667 }
1668 1668
1669 1669 #content div.box table td.selected {
1670 1670 width: 3%;
1671 1671 text-align: center;
1672 1672 vertical-align: middle;
1673 1673 padding: 0;
1674 1674 }
1675 1675
1676 1676 #content div.box table td.action {
1677 1677 width: 45%;
1678 1678 text-align: left;
1679 1679 }
1680 1680
1681 1681 #content div.box table td.date {
1682 1682 width: 33%;
1683 1683 text-align: center;
1684 1684 }
1685 1685
1686 1686 #content div.box div.action {
1687 1687 float: right;
1688 1688 background: #FFF;
1689 1689 text-align: right;
1690 1690 margin: 10px 0 0;
1691 1691 padding: 0;
1692 1692 }
1693 1693
1694 1694 #content div.box div.action select {
1695 1695 font-size: 11px;
1696 1696 margin: 0;
1697 1697 }
1698 1698
1699 1699 #content div.box div.action .ui-selectmenu {
1700 1700 margin: 0;
1701 1701 padding: 0;
1702 1702 }
1703 1703
1704 1704 #content div.box div.pagination {
1705 1705 height: 1%;
1706 1706 clear: both;
1707 1707 overflow: hidden;
1708 1708 margin: 10px 0 0;
1709 1709 padding: 0;
1710 1710 }
1711 1711
1712 1712 #content div.box div.pagination ul.pager {
1713 1713 float: right;
1714 1714 text-align: right;
1715 1715 margin: 0;
1716 1716 padding: 0;
1717 1717 }
1718 1718
1719 1719 #content div.box div.pagination ul.pager li {
1720 1720 height: 1%;
1721 1721 float: left;
1722 1722 list-style: none;
1723 1723 background: #ebebeb url("../images/pager.png") repeat-x;
1724 1724 border-top: 1px solid #dedede;
1725 1725 border-left: 1px solid #cfcfcf;
1726 1726 border-right: 1px solid #c4c4c4;
1727 1727 border-bottom: 1px solid #c4c4c4;
1728 1728 color: #4A4A4A;
1729 1729 font-weight: 700;
1730 1730 margin: 0 0 0 4px;
1731 1731 padding: 0;
1732 1732 }
1733 1733
1734 1734 #content div.box div.pagination ul.pager li.separator {
1735 1735 padding: 6px;
1736 1736 }
1737 1737
1738 1738 #content div.box div.pagination ul.pager li.current {
1739 1739 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1740 1740 border-top: 1px solid #ccc;
1741 1741 border-left: 1px solid #bebebe;
1742 1742 border-right: 1px solid #b1b1b1;
1743 1743 border-bottom: 1px solid #afafaf;
1744 1744 color: #515151;
1745 1745 padding: 6px;
1746 1746 }
1747 1747
1748 1748 #content div.box div.pagination ul.pager li a {
1749 1749 height: 1%;
1750 1750 display: block;
1751 1751 float: left;
1752 1752 color: #515151;
1753 1753 text-decoration: none;
1754 1754 margin: 0;
1755 1755 padding: 6px;
1756 1756 }
1757 1757
1758 1758 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1759 1759 {
1760 1760 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1761 1761 border-top: 1px solid #ccc;
1762 1762 border-left: 1px solid #bebebe;
1763 1763 border-right: 1px solid #b1b1b1;
1764 1764 border-bottom: 1px solid #afafaf;
1765 1765 margin: -1px;
1766 1766 }
1767 1767
1768 1768 #content div.box div.pagination-wh {
1769 1769 height: 1%;
1770 1770 clear: both;
1771 1771 overflow: hidden;
1772 1772 text-align: right;
1773 1773 margin: 10px 0 0;
1774 1774 padding: 0;
1775 1775 }
1776 1776
1777 1777 #content div.box div.pagination-right {
1778 1778 float: right;
1779 1779 }
1780 1780
1781 1781 #content div.box div.pagination-wh a,
1782 1782 #content div.box div.pagination-wh span.pager_dotdot,
1783 1783 #content div.box div.pagination-wh span.yui-pg-previous,
1784 1784 #content div.box div.pagination-wh span.yui-pg-last,
1785 1785 #content div.box div.pagination-wh span.yui-pg-next,
1786 1786 #content div.box div.pagination-wh span.yui-pg-first
1787 1787 {
1788 1788 height: 1%;
1789 1789 float: left;
1790 1790 background: #ebebeb url("../images/pager.png") repeat-x;
1791 1791 border-top: 1px solid #dedede;
1792 1792 border-left: 1px solid #cfcfcf;
1793 1793 border-right: 1px solid #c4c4c4;
1794 1794 border-bottom: 1px solid #c4c4c4;
1795 1795 color: #4A4A4A;
1796 1796 font-weight: 700;
1797 1797 margin: 0 0 0 4px;
1798 1798 padding: 6px;
1799 1799 }
1800 1800
1801 1801 #content div.box div.pagination-wh span.pager_curpage {
1802 1802 height: 1%;
1803 1803 float: left;
1804 1804 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1805 1805 border-top: 1px solid #ccc;
1806 1806 border-left: 1px solid #bebebe;
1807 1807 border-right: 1px solid #b1b1b1;
1808 1808 border-bottom: 1px solid #afafaf;
1809 1809 color: #515151;
1810 1810 font-weight: 700;
1811 1811 margin: 0 0 0 4px;
1812 1812 padding: 6px;
1813 1813 }
1814 1814
1815 1815 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1816 1816 {
1817 1817 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1818 1818 border-top: 1px solid #ccc;
1819 1819 border-left: 1px solid #bebebe;
1820 1820 border-right: 1px solid #b1b1b1;
1821 1821 border-bottom: 1px solid #afafaf;
1822 1822 text-decoration: none;
1823 1823 }
1824 1824
1825 1825 #content div.box div.traffic div.legend {
1826 1826 clear: both;
1827 1827 overflow: hidden;
1828 1828 border-bottom: 1px solid #ddd;
1829 1829 margin: 0 0 10px;
1830 1830 padding: 0 0 10px;
1831 1831 }
1832 1832
1833 1833 #content div.box div.traffic div.legend h6 {
1834 1834 float: left;
1835 1835 border: none;
1836 1836 margin: 0;
1837 1837 padding: 0;
1838 1838 }
1839 1839
1840 1840 #content div.box div.traffic div.legend li {
1841 1841 list-style: none;
1842 1842 float: left;
1843 1843 font-size: 11px;
1844 1844 margin: 0;
1845 1845 padding: 0 8px 0 4px;
1846 1846 }
1847 1847
1848 1848 #content div.box div.traffic div.legend li.visits {
1849 1849 border-left: 12px solid #edc240;
1850 1850 }
1851 1851
1852 1852 #content div.box div.traffic div.legend li.pageviews {
1853 1853 border-left: 12px solid #afd8f8;
1854 1854 }
1855 1855
1856 1856 #content div.box div.traffic table {
1857 1857 width: auto;
1858 1858 }
1859 1859
1860 1860 #content div.box div.traffic table td {
1861 1861 background: transparent;
1862 1862 border: none;
1863 1863 padding: 2px 3px 3px;
1864 1864 }
1865 1865
1866 1866 #content div.box div.traffic table td.legendLabel {
1867 1867 padding: 0 3px 2px;
1868 1868 }
1869 1869
1870 1870 #summary {
1871 1871
1872 1872 }
1873 1873
1874 1874 #summary .metatag {
1875 1875 display: inline-block;
1876 1876 padding: 3px 5px;
1877 1877 margin-bottom: 3px;
1878 1878 margin-right: 1px;
1879 1879 border-radius: 5px;
1880 1880 }
1881 1881
1882 1882 #content div.box #summary p {
1883 1883 margin-bottom: -5px;
1884 1884 width: 600px;
1885 1885 white-space: pre-wrap;
1886 1886 }
1887 1887
1888 1888 #content div.box #summary p:last-child {
1889 1889 margin-bottom: 9px;
1890 1890 }
1891 1891
1892 1892 #content div.box #summary p:first-of-type {
1893 1893 margin-top: 9px;
1894 1894 }
1895 1895
1896 1896 .metatag {
1897 1897 display: inline-block;
1898 1898 margin-right: 1px;
1899 1899 -webkit-border-radius: 4px 4px 4px 4px;
1900 1900 -khtml-border-radius: 4px 4px 4px 4px;
1901 1901 -moz-border-radius: 4px 4px 4px 4px;
1902 1902 border-radius: 4px 4px 4px 4px;
1903 1903
1904 1904 border: solid 1px #9CF;
1905 1905 padding: 2px 3px 2px 3px !important;
1906 1906 background-color: #DEF;
1907 1907 }
1908 1908
1909 1909 .metatag[tag="dead"] {
1910 1910 background-color: #E44;
1911 1911 }
1912 1912
1913 1913 .metatag[tag="stale"] {
1914 1914 background-color: #EA4;
1915 1915 }
1916 1916
1917 1917 .metatag[tag="featured"] {
1918 1918 background-color: #AEA;
1919 1919 }
1920 1920
1921 1921 .metatag[tag="requires"] {
1922 1922 background-color: #9CF;
1923 1923 }
1924 1924
1925 1925 .metatag[tag="recommends"] {
1926 1926 background-color: #BDF;
1927 1927 }
1928 1928
1929 1929 .metatag[tag="lang"] {
1930 1930 background-color: #FAF474;
1931 1931 }
1932 1932
1933 1933 .metatag[tag="license"] {
1934 1934 border: solid 1px #9CF;
1935 1935 background-color: #DEF;
1936 1936 target-new: tab !important;
1937 1937 }
1938 1938 .metatag[tag="see"] {
1939 1939 border: solid 1px #CBD;
1940 1940 background-color: #EDF;
1941 1941 }
1942 1942
1943 1943 a.metatag[tag="license"]:hover {
1944 1944 background-color: #003367;
1945 1945 color: #FFF;
1946 1946 text-decoration: none;
1947 1947 }
1948 1948
1949 1949 #summary .desc {
1950 1950 white-space: pre;
1951 1951 width: 100%;
1952 1952 }
1953 1953
1954 1954 #summary .repo_name {
1955 1955 font-size: 1.6em;
1956 1956 font-weight: bold;
1957 1957 vertical-align: baseline;
1958 1958 clear: right
1959 1959 }
1960 1960
1961 1961 #footer {
1962 1962 clear: both;
1963 1963 overflow: hidden;
1964 1964 text-align: right;
1965 1965 margin: 0;
1966 1966 padding: 0 10px 4px;
1967 1967 margin: -10px 0 0;
1968 1968 }
1969 1969
1970 1970 #footer div#footer-inner {
1971 1971 background-color: #003B76;
1972 1972 background-repeat : repeat-x;
1973 1973 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1974 1974 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1975 1975 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1976 1976 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1977 1977 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1978 1978 background-image : -o-linear-gradient( top, #003b76, #00376e));
1979 1979 background-image : linear-gradient( top, #003b76, #00376e);
1980 1980 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1981 1981 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1982 1982 -webkit-border-radius: 4px 4px 4px 4px;
1983 1983 -khtml-border-radius: 4px 4px 4px 4px;
1984 1984 -moz-border-radius: 4px 4px 4px 4px;
1985 1985 border-radius: 4px 4px 4px 4px;
1986 1986 }
1987 1987
1988 1988 #footer div#footer-inner p {
1989 1989 padding: 15px 25px 15px 0;
1990 1990 color: #FFF;
1991 1991 font-weight: 700;
1992 1992 }
1993 1993
1994 1994 #footer div#footer-inner .footer-link {
1995 1995 float: left;
1996 1996 padding-left: 10px;
1997 1997 }
1998 1998
1999 1999 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
2000 2000 {
2001 2001 color: #FFF;
2002 2002 }
2003 2003
2004 2004 #login div.title {
2005 width: 420px;
2006 2005 clear: both;
2007 2006 overflow: hidden;
2008 2007 position: relative;
2009 2008 background-color: #003B76;
2010 2009 background-repeat : repeat-x;
2011 2010 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
2012 2011 background-image : -moz-linear-gradient( top, #003b76, #00376e);
2013 2012 background-image : -ms-linear-gradient( top, #003b76, #00376e);
2014 2013 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
2015 2014 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
2016 2015 background-image : -o-linear-gradient( top, #003b76, #00376e));
2017 2016 background-image : linear-gradient( top, #003b76, #00376e);
2018 2017 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
2019 2018 margin: 0 auto;
2020 2019 padding: 0;
2021 2020 }
2022 2021
2023 2022 #login div.inner {
2024 width: 380px;
2025 2023 background: #FFF url("../images/login.png") no-repeat top left;
2026 2024 border-top: none;
2027 2025 border-bottom: none;
2028 2026 margin: 0 auto;
2029 2027 padding: 20px;
2030 2028 }
2031 2029
2032 2030 #login div.form div.fields div.field div.label {
2033 2031 width: 173px;
2034 2032 float: left;
2035 2033 text-align: right;
2036 2034 margin: 2px 10px 0 0;
2037 2035 padding: 5px 0 0 5px;
2038 2036 }
2039 2037
2040 2038 #login div.form div.fields div.field div.input input {
2041 width: 176px;
2042 2039 background: #FFF;
2043 2040 border-top: 1px solid #b3b3b3;
2044 2041 border-left: 1px solid #b3b3b3;
2045 2042 border-right: 1px solid #eaeaea;
2046 2043 border-bottom: 1px solid #eaeaea;
2047 2044 color: #000;
2048 2045 font-size: 11px;
2049 2046 margin: 0;
2050 2047 padding: 7px 7px 6px;
2051 2048 }
2052 2049
2053 2050 #login div.form div.fields div.buttons {
2054 2051 clear: both;
2055 2052 overflow: hidden;
2056 2053 border-top: 1px solid #DDD;
2057 2054 text-align: right;
2058 2055 margin: 0;
2059 2056 padding: 10px 0 0;
2060 2057 }
2061 2058
2062 2059 #login div.form div.links {
2063 2060 clear: both;
2064 2061 overflow: hidden;
2065 2062 margin: 10px 0 0;
2066 2063 padding: 0 0 2px;
2067 2064 }
2068 2065
2069 2066 .user-menu{
2070 2067 margin: 0px !important;
2071 2068 float: left;
2072 2069 }
2073 2070
2074 2071 .user-menu .container{
2075 2072 padding:0px 4px 0px 4px;
2076 2073 margin: 0px 0px 0px 0px;
2077 2074 }
2078 2075
2079 2076 .user-menu .gravatar{
2080 2077 margin: 0px 0px 0px 0px;
2081 2078 cursor: pointer;
2082 2079 }
2083 2080 .user-menu .gravatar.enabled{
2084 2081 background-color: #FDF784 !important;
2085 2082 }
2086 2083 .user-menu .gravatar:hover{
2087 2084 background-color: #FDF784 !important;
2088 2085 }
2089 2086 #quick_login{
2090 2087 min-height: 80px;
2091 2088 margin: 37px 0 0 -251px;
2092 2089 padding: 4px;
2093 2090 position: absolute;
2094 2091 width: 278px;
2095 2092 background-color: #003B76;
2096 2093 background-repeat: repeat-x;
2097 2094 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2098 2095 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2099 2096 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2100 2097 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2101 2098 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2102 2099 background-image: -o-linear-gradient(top, #003b76, #00376e);
2103 2100 background-image: linear-gradient(top, #003b76, #00376e);
2104 2101 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
2105 2102
2106 2103 z-index: 999;
2107 2104 -webkit-border-radius: 0px 0px 4px 4px;
2108 2105 -khtml-border-radius: 0px 0px 4px 4px;
2109 2106 -moz-border-radius: 0px 0px 4px 4px;
2110 2107 border-radius: 0px 0px 4px 4px;
2111 2108 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2112 2109 }
2113 2110 #quick_login h4{
2114 2111 color: #fff;
2115 2112 padding: 5px 0px 5px 14px;
2116 2113 }
2117 2114
2118 2115 #quick_login .password_forgoten {
2119 2116 padding-right: 10px;
2120 2117 padding-top: 0px;
2121 2118 text-align: left;
2122 2119 }
2123 2120
2124 2121 #quick_login .password_forgoten a {
2125 2122 font-size: 10px;
2126 2123 color: #fff;
2127 2124 }
2128 2125
2129 2126 #quick_login .register {
2130 2127 padding-right: 10px;
2131 2128 padding-top: 5px;
2132 2129 text-align: left;
2133 2130 }
2134 2131
2135 2132 #quick_login .register a {
2136 2133 font-size: 10px;
2137 2134 color: #fff;
2138 2135 }
2139 2136
2140 2137 #quick_login .submit {
2141 2138 margin: -20px 0 0 0px;
2142 2139 position: absolute;
2143 2140 right: 15px;
2144 2141 }
2145 2142
2146 2143 #quick_login .links_left{
2147 2144 float: left;
2148 2145 }
2149 2146 #quick_login .links_right{
2150 2147 float: right;
2151 2148 }
2152 2149 #quick_login .full_name{
2153 2150 color: #FFFFFF;
2154 2151 font-weight: bold;
2155 2152 padding: 3px;
2156 2153 }
2157 2154 #quick_login .big_gravatar{
2158 2155 padding:4px 0px 0px 6px;
2159 2156 }
2160 2157 #quick_login .inbox{
2161 2158 padding:4px 0px 0px 6px;
2162 2159 color: #FFFFFF;
2163 2160 font-weight: bold;
2164 2161 }
2165 2162 #quick_login .inbox a{
2166 2163 color: #FFFFFF;
2167 2164 }
2168 2165 #quick_login .email,#quick_login .email a{
2169 2166 color: #FFFFFF;
2170 2167 padding: 3px;
2171 2168
2172 2169 }
2173 2170 #quick_login .links .logout{
2174 2171
2175 2172 }
2176 2173
2177 2174 #quick_login div.form div.fields {
2178 2175 padding-top: 2px;
2179 2176 padding-left: 10px;
2180 2177 }
2181 2178
2182 2179 #quick_login div.form div.fields div.field {
2183 2180 padding: 5px;
2184 2181 }
2185 2182
2186 2183 #quick_login div.form div.fields div.field div.label label {
2187 2184 color: #fff;
2188 2185 padding-bottom: 3px;
2189 2186 }
2190 2187
2191 2188 #quick_login div.form div.fields div.field div.input input {
2192 2189 width: 236px;
2193 2190 background: #FFF;
2194 2191 border-top: 1px solid #b3b3b3;
2195 2192 border-left: 1px solid #b3b3b3;
2196 2193 border-right: 1px solid #eaeaea;
2197 2194 border-bottom: 1px solid #eaeaea;
2198 2195 color: #000;
2199 2196 font-size: 11px;
2200 2197 margin: 0;
2201 2198 padding: 5px 7px 4px;
2202 2199 }
2203 2200
2204 2201 #quick_login div.form div.fields div.buttons {
2205 2202 clear: both;
2206 2203 overflow: hidden;
2207 2204 text-align: right;
2208 2205 margin: 0;
2209 2206 padding: 5px 14px 0px 5px;
2210 2207 }
2211 2208
2212 2209 #quick_login div.form div.links {
2213 2210 clear: both;
2214 2211 overflow: hidden;
2215 2212 margin: 10px 0 0;
2216 2213 padding: 0 0 2px;
2217 2214 }
2218 2215
2219 2216 #quick_login ol.links{
2220 2217 display: block;
2221 2218 font-weight: bold;
2222 2219 list-style: none outside none;
2223 2220 text-align: right;
2224 2221 }
2225 2222 #quick_login ol.links li{
2226 2223 line-height: 27px;
2227 2224 margin: 0;
2228 2225 padding: 0;
2229 2226 color: #fff;
2230 2227 display: block;
2231 2228 float:none !important;
2232 2229 }
2233 2230
2234 2231 #quick_login ol.links li a{
2235 2232 color: #fff;
2236 2233 display: block;
2237 2234 padding: 2px;
2238 2235 }
2239 2236 #quick_login ol.links li a:HOVER{
2240 2237 background-color: inherit !important;
2241 2238 }
2242 2239
2243 2240 #register div.title {
2244 2241 clear: both;
2245 2242 overflow: hidden;
2246 2243 position: relative;
2247 2244 background-color: #003B76;
2248 2245 background-repeat: repeat-x;
2249 2246 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2250 2247 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2251 2248 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2252 2249 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2253 2250 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2254 2251 background-image: -o-linear-gradient(top, #003b76, #00376e);
2255 2252 background-image: linear-gradient(top, #003b76, #00376e);
2256 2253 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2257 2254 endColorstr='#00376e', GradientType=0 );
2258 2255 margin: 0 auto;
2259 2256 padding: 0;
2260 2257 }
2261 2258
2262 2259 #register div.inner {
2263 2260 background: #FFF;
2264 2261 border-top: none;
2265 2262 border-bottom: none;
2266 2263 margin: 0 auto;
2267 2264 padding: 20px;
2268 2265 }
2269 2266
2270 2267 #register div.form div.fields div.field div.label {
2271 2268 width: 135px;
2272 2269 float: left;
2273 2270 text-align: right;
2274 2271 margin: 2px 10px 0 0;
2275 2272 padding: 5px 0 0 5px;
2276 2273 }
2277 2274
2278 2275 #register div.form div.fields div.field div.input input {
2279 2276 width: 300px;
2280 2277 background: #FFF;
2281 2278 border-top: 1px solid #b3b3b3;
2282 2279 border-left: 1px solid #b3b3b3;
2283 2280 border-right: 1px solid #eaeaea;
2284 2281 border-bottom: 1px solid #eaeaea;
2285 2282 color: #000;
2286 2283 font-size: 11px;
2287 2284 margin: 0;
2288 2285 padding: 7px 7px 6px;
2289 2286 }
2290 2287
2291 2288 #register div.form div.fields div.buttons {
2292 2289 clear: both;
2293 2290 overflow: hidden;
2294 2291 border-top: 1px solid #DDD;
2295 2292 text-align: left;
2296 2293 margin: 0;
2297 2294 padding: 10px 0 0 150px;
2298 2295 }
2299 2296
2300 2297 #register div.form div.activation_msg {
2301 2298 padding-top: 4px;
2302 2299 padding-bottom: 4px;
2303 2300 }
2304 2301
2305 2302 #journal .journal_day {
2306 2303 font-size: 20px;
2307 2304 padding: 10px 0px;
2308 2305 border-bottom: 2px solid #DDD;
2309 2306 margin-left: 10px;
2310 2307 margin-right: 10px;
2311 2308 }
2312 2309
2313 2310 #journal .journal_container {
2314 2311 padding: 5px;
2315 2312 clear: both;
2316 2313 margin: 0px 5px 0px 10px;
2317 2314 }
2318 2315
2319 2316 #journal .journal_action_container {
2320 2317 padding-left: 38px;
2321 2318 }
2322 2319
2323 2320 #journal .journal_user {
2324 2321 color: #747474;
2325 2322 font-size: 14px;
2326 2323 font-weight: bold;
2327 2324 height: 30px;
2328 2325 }
2329 2326
2330 2327 #journal .journal_user.deleted {
2331 2328 color: #747474;
2332 2329 font-size: 14px;
2333 2330 font-weight: normal;
2334 2331 height: 30px;
2335 2332 font-style: italic;
2336 2333 }
2337 2334
2338 2335
2339 2336 #journal .journal_icon {
2340 2337 clear: both;
2341 2338 float: left;
2342 2339 padding-right: 4px;
2343 2340 padding-top: 3px;
2344 2341 }
2345 2342
2346 2343 #journal .journal_action {
2347 2344 padding-top: 4px;
2348 2345 min-height: 2px;
2349 2346 float: left
2350 2347 }
2351 2348
2352 2349 #journal .journal_action_params {
2353 2350 clear: left;
2354 2351 padding-left: 22px;
2355 2352 }
2356 2353
2357 2354 #journal .journal_repo {
2358 2355 float: left;
2359 2356 margin-left: 6px;
2360 2357 padding-top: 3px;
2361 2358 }
2362 2359
2363 2360 #journal .date {
2364 2361 clear: both;
2365 2362 color: #777777;
2366 2363 font-size: 11px;
2367 2364 padding-left: 22px;
2368 2365 }
2369 2366
2370 2367 #journal .journal_repo .journal_repo_name {
2371 2368 font-weight: bold;
2372 2369 font-size: 1.1em;
2373 2370 }
2374 2371
2375 2372 #journal .compare_view {
2376 2373 padding: 5px 0px 5px 0px;
2377 2374 width: 95px;
2378 2375 }
2379 2376
2380 2377 .journal_highlight {
2381 2378 font-weight: bold;
2382 2379 padding: 0 2px;
2383 2380 vertical-align: bottom;
2384 2381 }
2385 2382
2386 2383 .trending_language_tbl,.trending_language_tbl td {
2387 2384 border: 0 !important;
2388 2385 margin: 0 !important;
2389 2386 padding: 0 !important;
2390 2387 }
2391 2388
2392 2389 .trending_language_tbl,.trending_language_tbl tr {
2393 2390 border-spacing: 1px;
2394 2391 }
2395 2392
2396 2393 .trending_language {
2397 2394 background-color: #003367;
2398 2395 color: #FFF;
2399 2396 display: block;
2400 2397 min-width: 20px;
2401 2398 text-decoration: none;
2402 2399 height: 12px;
2403 2400 margin-bottom: 0px;
2404 2401 margin-left: 5px;
2405 2402 white-space: pre;
2406 2403 padding: 3px;
2407 2404 }
2408 2405
2409 2406 h3.files_location {
2410 2407 font-size: 1.8em;
2411 2408 font-weight: 700;
2412 2409 border-bottom: none !important;
2413 2410 margin: 10px 0 !important;
2414 2411 }
2415 2412
2416 2413 #files_data dl dt {
2417 2414 float: left;
2418 2415 width: 60px;
2419 2416 margin: 0 !important;
2420 2417 padding: 5px;
2421 2418 }
2422 2419
2423 2420 #files_data dl dd {
2424 2421 margin: 0 !important;
2425 2422 padding: 5px !important;
2426 2423 }
2427 2424
2428 2425 .file_history{
2429 2426 padding-top:10px;
2430 2427 font-size:16px;
2431 2428 }
2432 2429 .file_author{
2433 2430 float: left;
2434 2431 }
2435 2432
2436 2433 .file_author .item{
2437 2434 float:left;
2438 2435 padding:5px;
2439 2436 color: #888;
2440 2437 }
2441 2438
2442 2439 .tablerow0 {
2443 2440 background-color: #F8F8F8;
2444 2441 }
2445 2442
2446 2443 .tablerow1 {
2447 2444 background-color: #FFFFFF;
2448 2445 }
2449 2446
2450 2447 .changeset_id {
2451 2448 font-family: monospace;
2452 2449 color: #666666;
2453 2450 }
2454 2451
2455 2452 .changeset_hash {
2456 2453 color: #000000;
2457 2454 }
2458 2455
2459 2456 #changeset_content {
2460 2457 border-left: 1px solid #CCC;
2461 2458 border-right: 1px solid #CCC;
2462 2459 border-bottom: 1px solid #CCC;
2463 2460 padding: 5px;
2464 2461 }
2465 2462
2466 2463 #changeset_compare_view_content {
2467 2464 border: 1px solid #CCC;
2468 2465 padding: 5px;
2469 2466 }
2470 2467
2471 2468 #changeset_content .container {
2472 2469 min-height: 100px;
2473 2470 font-size: 1.2em;
2474 2471 overflow: hidden;
2475 2472 }
2476 2473
2477 2474 #changeset_compare_view_content .compare_view_commits {
2478 2475 width: auto !important;
2479 2476 }
2480 2477
2481 2478 #changeset_compare_view_content .compare_view_commits td {
2482 2479 padding: 0px 0px 0px 12px !important;
2483 2480 }
2484 2481
2485 2482 #changeset_content .container .right {
2486 2483 float: right;
2487 2484 width: 20%;
2488 2485 text-align: right;
2489 2486 }
2490 2487
2491 2488 #changeset_content .container .left .message {
2492 2489 white-space: pre-wrap;
2493 2490 }
2494 2491 #changeset_content .container .left .message a:hover {
2495 2492 text-decoration: none;
2496 2493 }
2497 2494 .cs_files .cur_cs {
2498 2495 margin: 10px 2px;
2499 2496 font-weight: bold;
2500 2497 }
2501 2498
2502 2499 .cs_files .node {
2503 2500 float: left;
2504 2501 }
2505 2502
2506 2503 .cs_files .changes {
2507 2504 float: right;
2508 2505 color:#003367;
2509 2506
2510 2507 }
2511 2508
2512 2509 .cs_files .changes .added {
2513 2510 background-color: #BBFFBB;
2514 2511 float: left;
2515 2512 text-align: center;
2516 2513 font-size: 9px;
2517 2514 padding: 2px 0px 2px 0px;
2518 2515 }
2519 2516
2520 2517 .cs_files .changes .deleted {
2521 2518 background-color: #FF8888;
2522 2519 float: left;
2523 2520 text-align: center;
2524 2521 font-size: 9px;
2525 2522 padding: 2px 0px 2px 0px;
2526 2523 }
2527 2524 /*new binary*/
2528 2525 .cs_files .changes .bin1 {
2529 2526 background-color: #BBFFBB;
2530 2527 float: left;
2531 2528 text-align: center;
2532 2529 font-size: 9px;
2533 2530 padding: 2px 0px 2px 0px;
2534 2531 }
2535 2532
2536 2533 /*deleted binary*/
2537 2534 .cs_files .changes .bin2 {
2538 2535 background-color: #FF8888;
2539 2536 float: left;
2540 2537 text-align: center;
2541 2538 font-size: 9px;
2542 2539 padding: 2px 0px 2px 0px;
2543 2540 }
2544 2541
2545 2542 /*mod binary*/
2546 2543 .cs_files .changes .bin3 {
2547 2544 background-color: #DDDDDD;
2548 2545 float: left;
2549 2546 text-align: center;
2550 2547 font-size: 9px;
2551 2548 padding: 2px 0px 2px 0px;
2552 2549 }
2553 2550
2554 2551 /*rename file*/
2555 2552 .cs_files .changes .bin4 {
2556 2553 background-color: #6D99FF;
2557 2554 float: left;
2558 2555 text-align: center;
2559 2556 font-size: 9px;
2560 2557 padding: 2px 0px 2px 0px;
2561 2558 }
2562 2559
2563 2560
2564 2561 .cs_files .cs_added,.cs_files .cs_A {
2565 2562 background: url("../images/icons/page_white_add.png") no-repeat scroll
2566 2563 3px;
2567 2564 height: 16px;
2568 2565 padding-left: 20px;
2569 2566 margin-top: 7px;
2570 2567 text-align: left;
2571 2568 }
2572 2569
2573 2570 .cs_files .cs_changed,.cs_files .cs_M {
2574 2571 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2575 2572 3px;
2576 2573 height: 16px;
2577 2574 padding-left: 20px;
2578 2575 margin-top: 7px;
2579 2576 text-align: left;
2580 2577 }
2581 2578
2582 2579 .cs_files .cs_removed,.cs_files .cs_D {
2583 2580 background: url("../images/icons/page_white_delete.png") no-repeat
2584 2581 scroll 3px;
2585 2582 height: 16px;
2586 2583 padding-left: 20px;
2587 2584 margin-top: 7px;
2588 2585 text-align: left;
2589 2586 }
2590 2587
2591 2588 #graph {
2592 2589 overflow: hidden;
2593 2590 }
2594 2591
2595 2592 #graph_nodes {
2596 2593 float: left;
2597 2594 margin-right: 0px;
2598 2595 margin-top: 0px;
2599 2596 }
2600 2597
2601 2598 #graph_content {
2602 2599 width: 80%;
2603 2600 float: left;
2604 2601 }
2605 2602
2606 2603 #graph_content .container_header {
2607 2604 border-bottom: 1px solid #DDD;
2608 2605 padding: 10px;
2609 2606 height: 25px;
2610 2607 }
2611 2608
2612 2609 #graph_content #rev_range_container {
2613 2610 float: left;
2614 2611 margin: 0px 0px 0px 3px;
2615 2612 }
2616 2613
2617 2614 #graph_content #rev_range_clear {
2618 2615 float: left;
2619 2616 margin: 0px 0px 0px 3px;
2620 2617 }
2621 2618
2622 2619 #graph_content .container {
2623 2620 border-bottom: 1px solid #DDD;
2624 2621 height: 56px;
2625 2622 overflow: hidden;
2626 2623 }
2627 2624
2628 2625 #graph_content .container .right {
2629 2626 float: right;
2630 2627 width: 23%;
2631 2628 text-align: right;
2632 2629 }
2633 2630
2634 2631 #graph_content .container .left {
2635 2632 float: left;
2636 2633 width: 25%;
2637 2634 padding-left: 5px;
2638 2635 }
2639 2636
2640 2637 #graph_content .container .mid {
2641 2638 float: left;
2642 2639 width: 49%;
2643 2640 }
2644 2641
2645 2642
2646 2643 #graph_content .container .left .date {
2647 2644 color: #666;
2648 2645 padding-left: 22px;
2649 2646 font-size: 10px;
2650 2647 }
2651 2648
2652 2649 #graph_content .container .left .author {
2653 2650 height: 22px;
2654 2651 }
2655 2652
2656 2653 #graph_content .container .left .author .user {
2657 2654 color: #444444;
2658 2655 float: left;
2659 2656 margin-left: -4px;
2660 2657 margin-top: 4px;
2661 2658 }
2662 2659
2663 2660 #graph_content .container .mid .message {
2664 2661 white-space: pre-wrap;
2665 2662 }
2666 2663
2667 2664 #graph_content .container .mid .message a:hover{
2668 2665 text-decoration: none;
2669 2666 }
2670 2667
2671 2668 .revision-link
2672 2669 {
2673 2670 color:#3F6F9F;
2674 2671 font-weight: bold !important;
2675 2672 }
2676 2673
2677 2674 .issue-tracker-link{
2678 2675 color:#3F6F9F;
2679 2676 font-weight: bold !important;
2680 2677 }
2681 2678
2682 2679 .changeset-status-container{
2683 2680 padding-right: 5px;
2684 2681 margin-top:1px;
2685 2682 float:right;
2686 2683 height:14px;
2687 2684 }
2688 2685 .code-header .changeset-status-container{
2689 2686 float:left;
2690 2687 padding:2px 0px 0px 2px;
2691 2688 }
2692 2689 .changeset-status-container .changeset-status-lbl{
2693 2690 color: rgb(136, 136, 136);
2694 2691 float: left;
2695 2692 padding: 3px 4px 0px 0px
2696 2693 }
2697 2694 .code-header .changeset-status-container .changeset-status-lbl{
2698 2695 float: left;
2699 2696 padding: 0px 4px 0px 0px;
2700 2697 }
2701 2698 .changeset-status-container .changeset-status-ico{
2702 2699 float: left;
2703 2700 }
2704 2701 .code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
2705 2702 float: left;
2706 2703 }
2707 2704 .right .comments-container{
2708 2705 padding-right: 5px;
2709 2706 margin-top:1px;
2710 2707 float:right;
2711 2708 height:14px;
2712 2709 }
2713 2710
2714 2711 .right .comments-cnt{
2715 2712 float: left;
2716 2713 color: rgb(136, 136, 136);
2717 2714 padding-right: 2px;
2718 2715 }
2719 2716
2720 2717 .right .changes{
2721 2718 clear: both;
2722 2719 }
2723 2720
2724 2721 .right .changes .changed_total {
2725 2722 display: block;
2726 2723 float: right;
2727 2724 text-align: center;
2728 2725 min-width: 45px;
2729 2726 cursor: pointer;
2730 2727 color: #444444;
2731 2728 background: #FEA;
2732 2729 -webkit-border-radius: 0px 0px 0px 6px;
2733 2730 -moz-border-radius: 0px 0px 0px 6px;
2734 2731 border-radius: 0px 0px 0px 6px;
2735 2732 padding: 1px;
2736 2733 }
2737 2734
2738 2735 .right .changes .added,.changed,.removed {
2739 2736 display: block;
2740 2737 padding: 1px;
2741 2738 color: #444444;
2742 2739 float: right;
2743 2740 text-align: center;
2744 2741 min-width: 15px;
2745 2742 }
2746 2743
2747 2744 .right .changes .added {
2748 2745 background: #CFC;
2749 2746 }
2750 2747
2751 2748 .right .changes .changed {
2752 2749 background: #FEA;
2753 2750 }
2754 2751
2755 2752 .right .changes .removed {
2756 2753 background: #FAA;
2757 2754 }
2758 2755
2759 2756 .right .merge {
2760 2757 padding: 1px 3px 1px 3px;
2761 2758 background-color: #fca062;
2762 2759 font-size: 10px;
2763 2760 font-weight: bold;
2764 2761 color: #ffffff;
2765 2762 text-transform: uppercase;
2766 2763 white-space: nowrap;
2767 2764 -webkit-border-radius: 3px;
2768 2765 -moz-border-radius: 3px;
2769 2766 border-radius: 3px;
2770 2767 margin-right: 2px;
2771 2768 }
2772 2769
2773 2770 .right .parent {
2774 2771 color: #666666;
2775 2772 clear:both;
2776 2773 }
2777 2774 .right .logtags{
2778 2775 padding: 2px 2px 2px 2px;
2779 2776 }
2780 2777 .right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
2781 2778 margin: 0px 2px;
2782 2779 }
2783 2780
2784 2781 .right .logtags .branchtag,.logtags .branchtag {
2785 2782 padding: 1px 3px 1px 3px;
2786 2783 background-color: #bfbfbf;
2787 2784 font-size: 10px;
2788 2785 font-weight: bold;
2789 2786 color: #ffffff;
2790 2787 text-transform: uppercase;
2791 2788 white-space: nowrap;
2792 2789 -webkit-border-radius: 3px;
2793 2790 -moz-border-radius: 3px;
2794 2791 border-radius: 3px;
2795 2792 }
2796 2793 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2797 2794 color: #ffffff;
2798 2795 }
2799 2796 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2800 2797 text-decoration: none;
2801 2798 color: #ffffff;
2802 2799 }
2803 2800 .right .logtags .tagtag,.logtags .tagtag {
2804 2801 padding: 1px 3px 1px 3px;
2805 2802 background-color: #62cffc;
2806 2803 font-size: 10px;
2807 2804 font-weight: bold;
2808 2805 color: #ffffff;
2809 2806 text-transform: uppercase;
2810 2807 white-space: nowrap;
2811 2808 -webkit-border-radius: 3px;
2812 2809 -moz-border-radius: 3px;
2813 2810 border-radius: 3px;
2814 2811 }
2815 2812 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2816 2813 color: #ffffff;
2817 2814 }
2818 2815 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2819 2816 text-decoration: none;
2820 2817 color: #ffffff;
2821 2818 }
2822 2819 .right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
2823 2820 padding: 1px 3px 1px 3px;
2824 2821 background-color: #46A546;
2825 2822 font-size: 10px;
2826 2823 font-weight: bold;
2827 2824 color: #ffffff;
2828 2825 text-transform: uppercase;
2829 2826 white-space: nowrap;
2830 2827 -webkit-border-radius: 3px;
2831 2828 -moz-border-radius: 3px;
2832 2829 border-radius: 3px;
2833 2830 }
2834 2831 .right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
2835 2832 color: #ffffff;
2836 2833 }
2837 2834 .right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
2838 2835 text-decoration: none;
2839 2836 color: #ffffff;
2840 2837 }
2841 2838 div.browserblock {
2842 2839 overflow: hidden;
2843 2840 border: 1px solid #ccc;
2844 2841 background: #f8f8f8;
2845 2842 font-size: 100%;
2846 2843 line-height: 125%;
2847 2844 padding: 0;
2848 2845 -webkit-border-radius: 6px 6px 0px 0px;
2849 2846 -moz-border-radius: 6px 6px 0px 0px;
2850 2847 border-radius: 6px 6px 0px 0px;
2851 2848 }
2852 2849
2853 2850 div.browserblock .browser-header {
2854 2851 background: #FFF;
2855 2852 padding: 10px 0px 15px 0px;
2856 2853 width: 100%;
2857 2854 }
2858 2855
2859 2856 div.browserblock .browser-nav {
2860 2857 float: left
2861 2858 }
2862 2859
2863 2860 div.browserblock .browser-branch {
2864 2861 float: left;
2865 2862 }
2866 2863
2867 2864 div.browserblock .browser-branch label {
2868 2865 color: #4A4A4A;
2869 2866 vertical-align: text-top;
2870 2867 }
2871 2868
2872 2869 div.browserblock .browser-header span {
2873 2870 margin-left: 5px;
2874 2871 font-weight: 700;
2875 2872 }
2876 2873
2877 2874 div.browserblock .browser-search {
2878 2875 clear: both;
2879 2876 padding: 8px 8px 0px 5px;
2880 2877 height: 20px;
2881 2878 }
2882 2879
2883 2880 div.browserblock #node_filter_box {
2884 2881
2885 2882 }
2886 2883
2887 2884 div.browserblock .search_activate {
2888 2885 float: left
2889 2886 }
2890 2887
2891 2888 div.browserblock .add_node {
2892 2889 float: left;
2893 2890 padding-left: 5px;
2894 2891 }
2895 2892
2896 2893 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2897 2894 {
2898 2895 text-decoration: none !important;
2899 2896 }
2900 2897
2901 2898 div.browserblock .browser-body {
2902 2899 background: #EEE;
2903 2900 border-top: 1px solid #CCC;
2904 2901 }
2905 2902
2906 2903 table.code-browser {
2907 2904 border-collapse: collapse;
2908 2905 width: 100%;
2909 2906 }
2910 2907
2911 2908 table.code-browser tr {
2912 2909 margin: 3px;
2913 2910 }
2914 2911
2915 2912 table.code-browser thead th {
2916 2913 background-color: #EEE;
2917 2914 height: 20px;
2918 2915 font-size: 1.1em;
2919 2916 font-weight: 700;
2920 2917 text-align: left;
2921 2918 padding-left: 10px;
2922 2919 }
2923 2920
2924 2921 table.code-browser tbody td {
2925 2922 padding-left: 10px;
2926 2923 height: 20px;
2927 2924 }
2928 2925
2929 2926 table.code-browser .browser-file {
2930 2927 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2931 2928 height: 16px;
2932 2929 padding-left: 20px;
2933 2930 text-align: left;
2934 2931 }
2935 2932 .diffblock .changeset_header {
2936 2933 height: 16px;
2937 2934 }
2938 2935 .diffblock .changeset_file {
2939 2936 background: url("../images/icons/file.png") no-repeat scroll 3px;
2940 2937 text-align: left;
2941 2938 float: left;
2942 2939 padding: 2px 0px 2px 22px;
2943 2940 }
2944 2941 .diffblock .diff-menu-wrapper{
2945 2942 float: left;
2946 2943 }
2947 2944
2948 2945 .diffblock .diff-menu{
2949 2946 position: absolute;
2950 2947 background: none repeat scroll 0 0 #FFFFFF;
2951 2948 border-color: #003367 #666666 #666666;
2952 2949 border-right: 1px solid #666666;
2953 2950 border-style: solid solid solid;
2954 2951 border-width: 1px;
2955 2952 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2956 2953 margin-top:5px;
2957 2954 margin-left:1px;
2958 2955
2959 2956 }
2960 2957 .diffblock .diff-actions {
2961 2958 padding: 2px 0px 0px 2px;
2962 2959 float: left;
2963 2960 }
2964 2961 .diffblock .diff-menu ul li {
2965 2962 padding: 0px 0px 0px 0px !important;
2966 2963 }
2967 2964 .diffblock .diff-menu ul li a{
2968 2965 display: block;
2969 2966 padding: 3px 8px 3px 8px !important;
2970 2967 }
2971 2968 .diffblock .diff-menu ul li a:hover{
2972 2969 text-decoration: none;
2973 2970 background-color: #EEEEEE;
2974 2971 }
2975 2972 table.code-browser .browser-dir {
2976 2973 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2977 2974 height: 16px;
2978 2975 padding-left: 20px;
2979 2976 text-align: left;
2980 2977 }
2981 2978
2982 2979 table.code-browser .submodule-dir {
2983 2980 background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
2984 2981 height: 16px;
2985 2982 padding-left: 20px;
2986 2983 text-align: left;
2987 2984 }
2988 2985
2989 2986
2990 2987 .box .search {
2991 2988 clear: both;
2992 2989 overflow: hidden;
2993 2990 margin: 0;
2994 2991 padding: 0 20px 10px;
2995 2992 }
2996 2993
2997 2994 .box .search div.search_path {
2998 2995 background: none repeat scroll 0 0 #EEE;
2999 2996 border: 1px solid #CCC;
3000 2997 color: blue;
3001 2998 margin-bottom: 10px;
3002 2999 padding: 10px 0;
3003 3000 }
3004 3001
3005 3002 .box .search div.search_path div.link {
3006 3003 font-weight: 700;
3007 3004 margin-left: 25px;
3008 3005 }
3009 3006
3010 3007 .box .search div.search_path div.link a {
3011 3008 color: #003367;
3012 3009 cursor: pointer;
3013 3010 text-decoration: none;
3014 3011 }
3015 3012
3016 3013 #path_unlock {
3017 3014 color: red;
3018 3015 font-size: 1.2em;
3019 3016 padding-left: 4px;
3020 3017 }
3021 3018
3022 3019 .info_box span {
3023 3020 margin-left: 3px;
3024 3021 margin-right: 3px;
3025 3022 }
3026 3023
3027 3024 .info_box .rev {
3028 3025 color: #003367;
3029 3026 font-size: 1.6em;
3030 3027 font-weight: bold;
3031 3028 vertical-align: sub;
3032 3029 }
3033 3030
3034 3031 .info_box input#at_rev,.info_box input#size {
3035 3032 background: #FFF;
3036 3033 border-top: 1px solid #b3b3b3;
3037 3034 border-left: 1px solid #b3b3b3;
3038 3035 border-right: 1px solid #eaeaea;
3039 3036 border-bottom: 1px solid #eaeaea;
3040 3037 color: #000;
3041 3038 font-size: 12px;
3042 3039 margin: 0;
3043 3040 padding: 1px 5px 1px;
3044 3041 }
3045 3042
3046 3043 .info_box input#view {
3047 3044 text-align: center;
3048 3045 padding: 4px 3px 2px 2px;
3049 3046 }
3050 3047
3051 3048 .yui-overlay,.yui-panel-container {
3052 3049 visibility: hidden;
3053 3050 position: absolute;
3054 3051 z-index: 2;
3055 3052 }
3056 3053
3057 3054 #tip-box {
3058 3055 position: absolute;
3059 3056
3060 3057 background-color: #FFF;
3061 3058 border: 2px solid #003367;
3062 3059 font: 100% sans-serif;
3063 3060 width: auto;
3064 3061 opacity: 1px;
3065 3062 padding: 8px;
3066 3063
3067 3064 white-space: pre-wrap;
3068 3065 -webkit-border-radius: 8px 8px 8px 8px;
3069 3066 -khtml-border-radius: 8px 8px 8px 8px;
3070 3067 -moz-border-radius: 8px 8px 8px 8px;
3071 3068 border-radius: 8px 8px 8px 8px;
3072 3069 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3073 3070 -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3074 3071 -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3075 3072 }
3076 3073
3077 3074 .hl-tip-box {
3078 3075 visibility: hidden;
3079 3076 position: absolute;
3080 3077 color: #666;
3081 3078 background-color: #FFF;
3082 3079 border: 2px solid #003367;
3083 3080 font: 100% sans-serif;
3084 3081 width: auto;
3085 3082 opacity: 1px;
3086 3083 padding: 8px;
3087 3084 white-space: pre-wrap;
3088 3085 -webkit-border-radius: 8px 8px 8px 8px;
3089 3086 -khtml-border-radius: 8px 8px 8px 8px;
3090 3087 -moz-border-radius: 8px 8px 8px 8px;
3091 3088 border-radius: 8px 8px 8px 8px;
3092 3089 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3093 3090 }
3094 3091
3095 3092
3096 3093 .mentions-container{
3097 3094 width: 90% !important;
3098 3095 }
3099 3096 .mentions-container .yui-ac-content{
3100 3097 width: 100% !important;
3101 3098 }
3102 3099
3103 3100 .ac {
3104 3101 vertical-align: top;
3105 3102 }
3106 3103
3107 3104 .ac .yui-ac {
3108 3105 position: inherit;
3109 3106 font-size: 100%;
3110 3107 }
3111 3108
3112 3109 .ac .perm_ac {
3113 3110 width: 20em;
3114 3111 }
3115 3112
3116 3113 .ac .yui-ac-input {
3117 3114 width: 100%;
3118 3115 }
3119 3116
3120 3117 .ac .yui-ac-container {
3121 3118 position: absolute;
3122 3119 top: 1.6em;
3123 3120 width: auto;
3124 3121 }
3125 3122
3126 3123 .ac .yui-ac-content {
3127 3124 position: absolute;
3128 3125 border: 1px solid gray;
3129 3126 background: #fff;
3130 3127 z-index: 9050;
3131 3128
3132 3129 }
3133 3130
3134 3131 .ac .yui-ac-shadow {
3135 3132 position: absolute;
3136 3133 width: 100%;
3137 3134 background: #000;
3138 3135 -moz-opacity: 0.1px;
3139 3136 opacity: .10;
3140 3137 filter: alpha(opacity = 10);
3141 3138 z-index: 9049;
3142 3139 margin: .3em;
3143 3140 }
3144 3141
3145 3142 .ac .yui-ac-content ul {
3146 3143 width: 100%;
3147 3144 margin: 0;
3148 3145 padding: 0;
3149 3146 z-index: 9050;
3150 3147 }
3151 3148
3152 3149 .ac .yui-ac-content li {
3153 3150 cursor: default;
3154 3151 white-space: nowrap;
3155 3152 margin: 0;
3156 3153 padding: 2px 5px;
3157 3154 height: 18px;
3158 3155 z-index: 9050;
3159 3156 display: block;
3160 3157 width: auto !important;
3161 3158 }
3162 3159
3163 3160 .ac .yui-ac-content li .ac-container-wrap{
3164 3161 width: auto;
3165 3162 }
3166 3163
3167 3164 .ac .yui-ac-content li.yui-ac-prehighlight {
3168 3165 background: #B3D4FF;
3169 3166 z-index: 9050;
3170 3167 }
3171 3168
3172 3169 .ac .yui-ac-content li.yui-ac-highlight {
3173 3170 background: #556CB5;
3174 3171 color: #FFF;
3175 3172 z-index: 9050;
3176 3173 }
3177 3174 .ac .yui-ac-bd{
3178 3175 z-index: 9050;
3179 3176 }
3180 3177
3181 3178 .follow {
3182 3179 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3183 3180 height: 16px;
3184 3181 width: 20px;
3185 3182 cursor: pointer;
3186 3183 display: block;
3187 3184 float: right;
3188 3185 margin-top: 2px;
3189 3186 }
3190 3187
3191 3188 .following {
3192 3189 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3193 3190 height: 16px;
3194 3191 width: 20px;
3195 3192 cursor: pointer;
3196 3193 display: block;
3197 3194 float: right;
3198 3195 margin-top: 2px;
3199 3196 }
3200 3197
3201 3198 .locking_locked{
3202 3199 background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
3203 3200 height: 16px;
3204 3201 width: 20px;
3205 3202 cursor: pointer;
3206 3203 display: block;
3207 3204 float: right;
3208 3205 margin-top: 2px;
3209 3206 }
3210 3207
3211 3208 .locking_unlocked{
3212 3209 background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
3213 3210 height: 16px;
3214 3211 width: 20px;
3215 3212 cursor: pointer;
3216 3213 display: block;
3217 3214 float: right;
3218 3215 margin-top: 2px;
3219 3216 }
3220 3217
3221 3218 .currently_following {
3222 3219 padding-left: 10px;
3223 3220 padding-bottom: 5px;
3224 3221 }
3225 3222
3226 3223 .add_icon {
3227 3224 background: url("../images/icons/add.png") no-repeat scroll 3px;
3228 3225 padding-left: 20px;
3229 3226 padding-top: 0px;
3230 3227 text-align: left;
3231 3228 }
3232 3229
3233 3230 .accept_icon {
3234 3231 background: url("../images/icons/accept.png") no-repeat scroll 3px;
3235 3232 padding-left: 20px;
3236 3233 padding-top: 0px;
3237 3234 text-align: left;
3238 3235 }
3239 3236
3240 3237 .edit_icon {
3241 3238 background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
3242 3239 padding-left: 20px;
3243 3240 padding-top: 0px;
3244 3241 text-align: left;
3245 3242 }
3246 3243
3247 3244 .delete_icon {
3248 3245 background: url("../images/icons/delete.png") no-repeat scroll 3px;
3249 3246 padding-left: 20px;
3250 3247 padding-top: 0px;
3251 3248 text-align: left;
3252 3249 }
3253 3250
3254 3251 .refresh_icon {
3255 3252 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3256 3253 3px;
3257 3254 padding-left: 20px;
3258 3255 padding-top: 0px;
3259 3256 text-align: left;
3260 3257 }
3261 3258
3262 3259 .pull_icon {
3263 3260 background: url("../images/icons/connect.png") no-repeat scroll 3px;
3264 3261 padding-left: 20px;
3265 3262 padding-top: 0px;
3266 3263 text-align: left;
3267 3264 }
3268 3265
3269 3266 .rss_icon {
3270 3267 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
3271 3268 padding-left: 20px;
3272 3269 padding-top: 4px;
3273 3270 text-align: left;
3274 3271 font-size: 8px
3275 3272 }
3276 3273
3277 3274 .atom_icon {
3278 3275 background: url("../images/icons/atom.png") no-repeat scroll 3px;
3279 3276 padding-left: 20px;
3280 3277 padding-top: 4px;
3281 3278 text-align: left;
3282 3279 font-size: 8px
3283 3280 }
3284 3281
3285 3282 .archive_icon {
3286 3283 background: url("../images/icons/compress.png") no-repeat scroll 3px;
3287 3284 padding-left: 20px;
3288 3285 text-align: left;
3289 3286 padding-top: 1px;
3290 3287 }
3291 3288
3292 3289 .start_following_icon {
3293 3290 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3294 3291 padding-left: 20px;
3295 3292 text-align: left;
3296 3293 padding-top: 0px;
3297 3294 }
3298 3295
3299 3296 .stop_following_icon {
3300 3297 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3301 3298 padding-left: 20px;
3302 3299 text-align: left;
3303 3300 padding-top: 0px;
3304 3301 }
3305 3302
3306 3303 .action_button {
3307 3304 border: 0;
3308 3305 display: inline;
3309 3306 }
3310 3307
3311 3308 .action_button:hover {
3312 3309 border: 0;
3313 3310 text-decoration: underline;
3314 3311 cursor: pointer;
3315 3312 }
3316 3313
3317 3314 #switch_repos {
3318 3315 position: absolute;
3319 3316 height: 25px;
3320 3317 z-index: 1;
3321 3318 }
3322 3319
3323 3320 #switch_repos select {
3324 3321 min-width: 150px;
3325 3322 max-height: 250px;
3326 3323 z-index: 1;
3327 3324 }
3328 3325
3329 3326 .breadcrumbs {
3330 3327 border: medium none;
3331 3328 color: #FFF;
3332 3329 float: left;
3333 3330 text-transform: uppercase;
3334 3331 font-weight: 700;
3335 3332 font-size: 14px;
3336 3333 margin: 0;
3337 3334 padding: 11px 0 11px 10px;
3338 3335 }
3339 3336
3340 3337 .breadcrumbs .hash {
3341 3338 text-transform: none;
3342 3339 color: #fff;
3343 3340 }
3344 3341
3345 3342 .breadcrumbs a {
3346 3343 color: #FFF;
3347 3344 }
3348 3345
3349 3346 .flash_msg {
3350 3347
3351 3348 }
3352 3349
3353 3350 .flash_msg ul {
3354 3351
3355 3352 }
3356 3353
3357 3354 .error_red {
3358 3355 color:red;
3359 3356 }
3360 3357
3361 3358 .error_msg {
3362 3359 background-color: #c43c35;
3363 3360 background-repeat: repeat-x;
3364 3361 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3365 3362 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3366 3363 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3367 3364 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3368 3365 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3369 3366 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3370 3367 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3371 3368 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3372 3369 border-color: #c43c35 #c43c35 #882a25;
3373 3370 }
3374 3371
3375 3372 .warning_msg {
3376 3373 color: #404040 !important;
3377 3374 background-color: #eedc94;
3378 3375 background-repeat: repeat-x;
3379 3376 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3380 3377 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3381 3378 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3382 3379 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3383 3380 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3384 3381 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3385 3382 background-image: linear-gradient(top, #fceec1, #eedc94);
3386 3383 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3387 3384 border-color: #eedc94 #eedc94 #e4c652;
3388 3385 }
3389 3386
3390 3387 .success_msg {
3391 3388 background-color: #57a957;
3392 3389 background-repeat: repeat-x !important;
3393 3390 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3394 3391 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3395 3392 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3396 3393 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3397 3394 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3398 3395 background-image: -o-linear-gradient(top, #62c462, #57a957);
3399 3396 background-image: linear-gradient(top, #62c462, #57a957);
3400 3397 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3401 3398 border-color: #57a957 #57a957 #3d773d;
3402 3399 }
3403 3400
3404 3401 .notice_msg {
3405 3402 background-color: #339bb9;
3406 3403 background-repeat: repeat-x;
3407 3404 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3408 3405 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3409 3406 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3410 3407 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3411 3408 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3412 3409 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3413 3410 background-image: linear-gradient(top, #5bc0de, #339bb9);
3414 3411 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3415 3412 border-color: #339bb9 #339bb9 #22697d;
3416 3413 }
3417 3414
3418 3415 .success_msg,.error_msg,.notice_msg,.warning_msg {
3419 3416 font-size: 12px;
3420 3417 font-weight: 700;
3421 3418 min-height: 14px;
3422 3419 line-height: 14px;
3423 3420 margin-bottom: 10px;
3424 3421 margin-top: 0;
3425 3422 display: block;
3426 3423 overflow: auto;
3427 3424 padding: 6px 10px 6px 10px;
3428 3425 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3429 3426 position: relative;
3430 3427 color: #FFF;
3431 3428 border-width: 1px;
3432 3429 border-style: solid;
3433 3430 -webkit-border-radius: 4px;
3434 3431 -moz-border-radius: 4px;
3435 3432 border-radius: 4px;
3436 3433 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3437 3434 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3438 3435 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3439 3436 }
3440 3437
3441 3438 #msg_close {
3442 3439 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3443 3440 cursor: pointer;
3444 3441 height: 16px;
3445 3442 position: absolute;
3446 3443 right: 5px;
3447 3444 top: 5px;
3448 3445 width: 16px;
3449 3446 }
3450 3447 div#legend_data{
3451 3448 padding-left:10px;
3452 3449 }
3453 3450 div#legend_container table{
3454 3451 border: none !important;
3455 3452 }
3456 3453 div#legend_container table,div#legend_choices table {
3457 3454 width: auto !important;
3458 3455 }
3459 3456
3460 3457 table#permissions_manage {
3461 3458 width: 0 !important;
3462 3459 }
3463 3460
3464 3461 table#permissions_manage span.private_repo_msg {
3465 3462 font-size: 0.8em;
3466 3463 opacity: 0.6px;
3467 3464 }
3468 3465
3469 3466 table#permissions_manage td.private_repo_msg {
3470 3467 font-size: 0.8em;
3471 3468 }
3472 3469
3473 3470 table#permissions_manage tr#add_perm_input td {
3474 3471 vertical-align: middle;
3475 3472 }
3476 3473
3477 3474 div.gravatar {
3478 3475 background-color: #FFF;
3479 3476 float: left;
3480 3477 margin-right: 0.7em;
3481 3478 padding: 1px 1px 1px 1px;
3482 3479 line-height:0;
3483 3480 -webkit-border-radius: 3px;
3484 3481 -khtml-border-radius: 3px;
3485 3482 -moz-border-radius: 3px;
3486 3483 border-radius: 3px;
3487 3484 }
3488 3485
3489 3486 div.gravatar img {
3490 3487 -webkit-border-radius: 2px;
3491 3488 -khtml-border-radius: 2px;
3492 3489 -moz-border-radius: 2px;
3493 3490 border-radius: 2px;
3494 3491 }
3495 3492
3496 3493 #header,#content,#footer {
3497 3494 min-width: 978px;
3498 3495 }
3499 3496
3500 3497 #content {
3501 3498 clear: both;
3502 3499 overflow: hidden;
3503 3500 padding: 54px 10px 14px 10px;
3504 3501 }
3505 3502
3506 3503 #content div.box div.title div.search {
3507 3504
3508 3505 border-left: 1px solid #316293;
3509 3506 }
3510 3507
3511 3508 #content div.box div.title div.search div.input input {
3512 3509 border: 1px solid #316293;
3513 3510 }
3514 3511
3515 3512 .ui-btn{
3516 3513 color: #515151;
3517 3514 background-color: #DADADA;
3518 3515 background-repeat: repeat-x;
3519 3516 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3520 3517 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3521 3518 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3522 3519 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3523 3520 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3524 3521 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3525 3522 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3526 3523 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3527 3524
3528 3525 border-top: 1px solid #DDD;
3529 3526 border-left: 1px solid #c6c6c6;
3530 3527 border-right: 1px solid #DDD;
3531 3528 border-bottom: 1px solid #c6c6c6;
3532 3529 color: #515151;
3533 3530 outline: none;
3534 3531 margin: 0px 3px 3px 0px;
3535 3532 -webkit-border-radius: 4px 4px 4px 4px !important;
3536 3533 -khtml-border-radius: 4px 4px 4px 4px !important;
3537 3534 -moz-border-radius: 4px 4px 4px 4px !important;
3538 3535 border-radius: 4px 4px 4px 4px !important;
3539 3536 cursor: pointer !important;
3540 3537 padding: 3px 3px 3px 3px;
3541 3538 background-position: 0 -15px;
3542 3539
3543 3540 }
3544 3541 .ui-btn.xsmall{
3545 3542 padding: 1px 2px 1px 1px;
3546 3543 }
3547 3544
3548 3545 .ui-btn.large{
3549 3546 padding: 6px 12px;
3550 3547 }
3551 3548
3552 3549 .ui-btn.clone{
3553 3550 padding: 5px 2px 6px 1px;
3554 3551 margin: 0px -4px 3px 0px;
3555 3552 -webkit-border-radius: 4px 0px 0px 4px !important;
3556 3553 -khtml-border-radius: 4px 0px 0px 4px !important;
3557 3554 -moz-border-radius: 4px 0px 0px 4px !important;
3558 3555 border-radius: 4px 0px 0px 4px !important;
3559 3556 width: 100px;
3560 3557 text-align: center;
3561 3558 float: left;
3562 3559 position: absolute;
3563 3560 }
3564 3561 .ui-btn:focus {
3565 3562 outline: none;
3566 3563 }
3567 3564 .ui-btn:hover{
3568 3565 background-position: 0 0px;
3569 3566 text-decoration: none;
3570 3567 color: #515151;
3571 3568 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3572 3569 }
3573 3570
3574 3571 .ui-btn.red{
3575 3572 color:#fff;
3576 3573 background-color: #c43c35;
3577 3574 background-repeat: repeat-x;
3578 3575 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3579 3576 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3580 3577 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3581 3578 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3582 3579 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3583 3580 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3584 3581 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3585 3582 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3586 3583 border-color: #c43c35 #c43c35 #882a25;
3587 3584 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3588 3585 }
3589 3586
3590 3587
3591 3588 .ui-btn.blue{
3592 3589 color:#fff;
3593 3590 background-color: #339bb9;
3594 3591 background-repeat: repeat-x;
3595 3592 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3596 3593 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3597 3594 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3598 3595 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3599 3596 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3600 3597 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3601 3598 background-image: linear-gradient(top, #5bc0de, #339bb9);
3602 3599 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3603 3600 border-color: #339bb9 #339bb9 #22697d;
3604 3601 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3605 3602 }
3606 3603
3607 3604 .ui-btn.green{
3608 3605 background-color: #57a957;
3609 3606 background-repeat: repeat-x;
3610 3607 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3611 3608 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3612 3609 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3613 3610 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3614 3611 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3615 3612 background-image: -o-linear-gradient(top, #62c462, #57a957);
3616 3613 background-image: linear-gradient(top, #62c462, #57a957);
3617 3614 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3618 3615 border-color: #57a957 #57a957 #3d773d;
3619 3616 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3620 3617 }
3621 3618
3622 3619 .ui-btn.blue.hidden{
3623 3620 display: none;
3624 3621 }
3625 3622
3626 3623 .ui-btn.active{
3627 3624 font-weight: bold;
3628 3625 }
3629 3626
3630 3627 ins,div.options a:hover {
3631 3628 text-decoration: none;
3632 3629 }
3633 3630
3634 3631 img,
3635 3632 #header #header-inner #quick li a:hover span.normal,
3636 3633 #header #header-inner #quick li ul li.last,
3637 3634 #content div.box div.form div.fields div.field div.textarea table td table td a,
3638 3635 #clone_url,
3639 3636 #clone_url_id
3640 3637 {
3641 3638 border: none;
3642 3639 }
3643 3640
3644 3641 img.icon,.right .merge img {
3645 3642 vertical-align: bottom;
3646 3643 }
3647 3644
3648 3645 #header ul#logged-user,#content div.box div.title ul.links,
3649 3646 #content div.box div.message div.dismiss,
3650 3647 #content div.box div.traffic div.legend ul
3651 3648 {
3652 3649 float: right;
3653 3650 margin: 0;
3654 3651 padding: 0;
3655 3652 }
3656 3653
3657 3654 #header #header-inner #home,#header #header-inner #logo,
3658 3655 #content div.box ul.left,#content div.box ol.left,
3659 3656 #content div.box div.pagination-left,div#commit_history,
3660 3657 div#legend_data,div#legend_container,div#legend_choices
3661 3658 {
3662 3659 float: left;
3663 3660 }
3664 3661
3665 3662 #header #header-inner #quick li:hover ul ul,
3666 3663 #header #header-inner #quick li:hover ul ul ul,
3667 3664 #header #header-inner #quick li:hover ul ul ul ul,
3668 3665 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3669 3666 {
3670 3667 display: none;
3671 3668 }
3672 3669
3673 3670 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
3674 3671 {
3675 3672 display: block;
3676 3673 }
3677 3674
3678 3675 #content div.graph {
3679 3676 padding: 0 10px 10px;
3680 3677 }
3681 3678
3682 3679 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3683 3680 {
3684 3681 color: #bfe3ff;
3685 3682 }
3686 3683
3687 3684 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
3688 3685 {
3689 3686 margin: 10px 24px 10px 44px;
3690 3687 }
3691 3688
3692 3689 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3693 3690 {
3694 3691 clear: both;
3695 3692 overflow: hidden;
3696 3693 margin: 0;
3697 3694 padding: 0 20px 10px;
3698 3695 }
3699 3696
3700 3697 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3701 3698 {
3702 3699 clear: both;
3703 3700 overflow: hidden;
3704 3701 margin: 0;
3705 3702 padding: 0;
3706 3703 }
3707 3704
3708 3705 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
3709 3706 {
3710 3707 height: 1%;
3711 3708 display: block;
3712 3709 color: #363636;
3713 3710 margin: 0;
3714 3711 padding: 2px 0 0;
3715 3712 }
3716 3713
3717 3714 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
3718 3715 {
3719 3716 background: #FBE3E4;
3720 3717 border-top: 1px solid #e1b2b3;
3721 3718 border-left: 1px solid #e1b2b3;
3722 3719 border-right: 1px solid #FBC2C4;
3723 3720 border-bottom: 1px solid #FBC2C4;
3724 3721 }
3725 3722
3726 3723 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
3727 3724 {
3728 3725 background: #E6EFC2;
3729 3726 border-top: 1px solid #cebb98;
3730 3727 border-left: 1px solid #cebb98;
3731 3728 border-right: 1px solid #c6d880;
3732 3729 border-bottom: 1px solid #c6d880;
3733 3730 }
3734 3731
3735 3732 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
3736 3733 {
3737 3734 margin: 0;
3738 3735 }
3739 3736
3740 3737 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
3741 3738 {
3742 3739 margin: 0 0 0 0px !important;
3743 3740 padding: 0;
3744 3741 }
3745 3742
3746 3743 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
3747 3744 {
3748 3745 margin: 0 0 0 200px;
3749 3746 padding: 0;
3750 3747 }
3751 3748
3752 3749 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
3753 3750 {
3754 3751 color: #000;
3755 3752 text-decoration: none;
3756 3753 }
3757 3754
3758 3755 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3759 3756 {
3760 3757 border: 1px solid #666;
3761 3758 }
3762 3759
3763 3760 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
3764 3761 {
3765 3762 clear: both;
3766 3763 overflow: hidden;
3767 3764 margin: 0;
3768 3765 padding: 8px 0 2px;
3769 3766 }
3770 3767
3771 3768 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
3772 3769 {
3773 3770 float: left;
3774 3771 margin: 0;
3775 3772 }
3776 3773
3777 3774 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
3778 3775 {
3779 3776 height: 1%;
3780 3777 display: block;
3781 3778 float: left;
3782 3779 margin: 2px 0 0 4px;
3783 3780 }
3784 3781
3785 3782 div.form div.fields div.field div.button input,
3786 3783 #content div.box div.form div.fields div.buttons input
3787 3784 div.form div.fields div.buttons input,
3788 3785 #content div.box div.action div.button input {
3789 3786 /*color: #000;*/
3790 3787 font-size: 11px;
3791 3788 font-weight: 700;
3792 3789 margin: 0;
3793 3790 }
3794 3791
3795 3792 input.ui-button {
3796 3793 background: #e5e3e3 url("../images/button.png") repeat-x;
3797 3794 border-top: 1px solid #DDD;
3798 3795 border-left: 1px solid #c6c6c6;
3799 3796 border-right: 1px solid #DDD;
3800 3797 border-bottom: 1px solid #c6c6c6;
3801 3798 color: #515151 !important;
3802 3799 outline: none;
3803 3800 margin: 0;
3804 3801 padding: 6px 12px;
3805 3802 -webkit-border-radius: 4px 4px 4px 4px;
3806 3803 -khtml-border-radius: 4px 4px 4px 4px;
3807 3804 -moz-border-radius: 4px 4px 4px 4px;
3808 3805 border-radius: 4px 4px 4px 4px;
3809 3806 box-shadow: 0 1px 0 #ececec;
3810 3807 cursor: pointer;
3811 3808 }
3812 3809
3813 3810 input.ui-button:hover {
3814 3811 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3815 3812 border-top: 1px solid #ccc;
3816 3813 border-left: 1px solid #bebebe;
3817 3814 border-right: 1px solid #b1b1b1;
3818 3815 border-bottom: 1px solid #afafaf;
3819 3816 }
3820 3817
3821 3818 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3822 3819 {
3823 3820 display: inline;
3824 3821 }
3825 3822
3826 3823 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3827 3824 {
3828 3825 margin: 10px 0 0 200px;
3829 3826 padding: 0;
3830 3827 }
3831 3828
3832 3829 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
3833 3830 {
3834 3831 margin: 10px 0 0;
3835 3832 }
3836 3833
3837 3834 #content div.box table td.user,#content div.box table td.address {
3838 3835 width: 10%;
3839 3836 text-align: center;
3840 3837 }
3841 3838
3842 3839 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
3843 3840 {
3844 3841 text-align: right;
3845 3842 margin: 6px 0 0;
3846 3843 padding: 0;
3847 3844 }
3848 3845
3849 3846 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
3850 3847 {
3851 3848 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3852 3849 border-top: 1px solid #ccc;
3853 3850 border-left: 1px solid #bebebe;
3854 3851 border-right: 1px solid #b1b1b1;
3855 3852 border-bottom: 1px solid #afafaf;
3856 3853 color: #515151;
3857 3854 margin: 0;
3858 3855 padding: 6px 12px;
3859 3856 }
3860 3857
3861 3858 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3862 3859 {
3863 3860 text-align: left;
3864 3861 float: left;
3865 3862 margin: 0;
3866 3863 padding: 0;
3867 3864 }
3868 3865
3869 3866 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3870 3867 {
3871 3868 height: 1%;
3872 3869 display: block;
3873 3870 float: left;
3874 3871 background: #ebebeb url("../images/pager.png") repeat-x;
3875 3872 border-top: 1px solid #dedede;
3876 3873 border-left: 1px solid #cfcfcf;
3877 3874 border-right: 1px solid #c4c4c4;
3878 3875 border-bottom: 1px solid #c4c4c4;
3879 3876 color: #4A4A4A;
3880 3877 font-weight: 700;
3881 3878 margin: 0;
3882 3879 padding: 6px 8px;
3883 3880 }
3884 3881
3885 3882 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3886 3883 {
3887 3884 color: #B4B4B4;
3888 3885 padding: 6px;
3889 3886 }
3890 3887
3891 3888 #login,#register {
3892 3889 width: 520px;
3893 3890 margin: 10% auto 0;
3894 3891 padding: 0;
3895 3892 }
3896 3893
3897 3894 #login div.color,#register div.color {
3898 3895 clear: both;
3899 3896 overflow: hidden;
3900 3897 background: #FFF;
3901 3898 margin: 10px auto 0;
3902 3899 padding: 3px 3px 3px 0;
3903 3900 }
3904 3901
3905 3902 #login div.color a,#register div.color a {
3906 3903 width: 20px;
3907 3904 height: 20px;
3908 3905 display: block;
3909 3906 float: left;
3910 3907 margin: 0 0 0 3px;
3911 3908 padding: 0;
3912 3909 }
3913 3910
3914 3911 #login div.title h5,#register div.title h5 {
3915 3912 color: #fff;
3916 3913 margin: 10px;
3917 3914 padding: 0;
3918 3915 }
3919 3916
3920 3917 #login div.form div.fields div.field,#register div.form div.fields div.field
3921 3918 {
3922 3919 clear: both;
3923 3920 overflow: hidden;
3924 3921 margin: 0;
3925 3922 padding: 0 0 10px;
3926 3923 }
3927 3924
3928 3925 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3929 3926 {
3930 3927 height: 1%;
3931 3928 display: block;
3932 3929 color: red;
3933 3930 margin: 8px 0 0;
3934 3931 padding: 0;
3935 3932 max-width: 320px;
3936 3933 }
3937 3934
3938 3935 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3939 3936 {
3940 3937 color: #000;
3941 3938 font-weight: 700;
3942 3939 }
3943 3940
3944 3941 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3945 3942 {
3946 3943 float: left;
3947 3944 margin: 0;
3948 3945 padding: 0;
3949 3946 }
3950 3947
3951 3948 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3952 3949 {
3953 3950 margin: 0 0 0 184px;
3954 3951 padding: 0;
3955 3952 }
3956 3953
3957 3954 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3958 3955 {
3959 3956 color: #565656;
3960 3957 font-weight: 700;
3961 3958 }
3962 3959
3963 3960 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3964 3961 {
3965 3962 color: #000;
3966 3963 font-size: 1em;
3967 3964 font-weight: 700;
3968 3965 margin: 0;
3969 3966 }
3970 3967
3971 3968 #changeset_content .container .wrapper,#graph_content .container .wrapper
3972 3969 {
3973 3970 width: 600px;
3974 3971 }
3975 3972
3976 3973 #changeset_content .container .left {
3977 3974 float: left;
3978 3975 width: 75%;
3979 3976 padding-left: 5px;
3980 3977 }
3981 3978
3982 3979 #changeset_content .container .left .date,.ac .match {
3983 3980 font-weight: 700;
3984 3981 padding-top: 5px;
3985 3982 padding-bottom: 5px;
3986 3983 }
3987 3984
3988 3985 div#legend_container table td,div#legend_choices table td {
3989 3986 border: none !important;
3990 3987 height: 20px !important;
3991 3988 padding: 0 !important;
3992 3989 }
3993 3990
3994 3991 .q_filter_box {
3995 3992 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3996 3993 -webkit-border-radius: 4px;
3997 3994 -moz-border-radius: 4px;
3998 3995 border-radius: 4px;
3999 3996 border: 0 none;
4000 3997 color: #AAAAAA;
4001 3998 margin-bottom: -4px;
4002 3999 margin-top: -4px;
4003 4000 padding-left: 3px;
4004 4001 }
4005 4002
4006 4003 #node_filter {
4007 4004 border: 0px solid #545454;
4008 4005 color: #AAAAAA;
4009 4006 padding-left: 3px;
4010 4007 }
4011 4008
4012 4009
4013 4010 .group_members_wrap{
4014 4011 min-height: 85px;
4015 4012 padding-left: 20px;
4016 4013 }
4017 4014
4018 4015 .group_members .group_member{
4019 4016 height: 30px;
4020 4017 padding:0px 0px 0px 0px;
4021 4018 }
4022 4019
4023 4020 .reviewers_member{
4024 4021 height: 15px;
4025 4022 padding:0px 0px 0px 10px;
4026 4023 }
4027 4024
4028 4025 .emails_wrap{
4029 4026 padding: 0px 20px;
4030 4027 }
4031 4028
4032 4029 .emails_wrap .email_entry{
4033 4030 height: 30px;
4034 4031 padding:0px 0px 0px 10px;
4035 4032 }
4036 4033 .emails_wrap .email_entry .email{
4037 4034 float: left
4038 4035 }
4039 4036 .emails_wrap .email_entry .email_action{
4040 4037 float: left
4041 4038 }
4042 4039
4043 4040 .ips_wrap{
4044 4041 padding: 0px 20px;
4045 4042 }
4046 4043
4047 4044 .ips_wrap .ip_entry{
4048 4045 height: 30px;
4049 4046 padding:0px 0px 0px 10px;
4050 4047 }
4051 4048 .ips_wrap .ip_entry .ip{
4052 4049 float: left
4053 4050 }
4054 4051 .ips_wrap .ip_entry .ip_action{
4055 4052 float: left
4056 4053 }
4057 4054
4058 4055
4059 4056 /*README STYLE*/
4060 4057
4061 4058 div.readme {
4062 4059 padding:0px;
4063 4060 }
4064 4061
4065 4062 div.readme h2 {
4066 4063 font-weight: normal;
4067 4064 }
4068 4065
4069 4066 div.readme .readme_box {
4070 4067 background-color: #fafafa;
4071 4068 }
4072 4069
4073 4070 div.readme .readme_box {
4074 4071 clear:both;
4075 4072 overflow:hidden;
4076 4073 margin:0;
4077 4074 padding:0 20px 10px;
4078 4075 }
4079 4076
4080 4077 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
4081 4078 border-bottom: 0 !important;
4082 4079 margin: 0 !important;
4083 4080 padding: 0 !important;
4084 4081 line-height: 1.5em !important;
4085 4082 }
4086 4083
4087 4084
4088 4085 div.readme .readme_box h1:first-child {
4089 4086 padding-top: .25em !important;
4090 4087 }
4091 4088
4092 4089 div.readme .readme_box h2, div.readme .readme_box h3 {
4093 4090 margin: 1em 0 !important;
4094 4091 }
4095 4092
4096 4093 div.readme .readme_box h2 {
4097 4094 margin-top: 1.5em !important;
4098 4095 border-top: 4px solid #e0e0e0 !important;
4099 4096 padding-top: .5em !important;
4100 4097 }
4101 4098
4102 4099 div.readme .readme_box p {
4103 4100 color: black !important;
4104 4101 margin: 1em 0 !important;
4105 4102 line-height: 1.5em !important;
4106 4103 }
4107 4104
4108 4105 div.readme .readme_box ul {
4109 4106 list-style: disc !important;
4110 4107 margin: 1em 0 1em 2em !important;
4111 4108 }
4112 4109
4113 4110 div.readme .readme_box ol {
4114 4111 list-style: decimal;
4115 4112 margin: 1em 0 1em 2em !important;
4116 4113 }
4117 4114
4118 4115 div.readme .readme_box pre, code {
4119 4116 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4120 4117 }
4121 4118
4122 4119 div.readme .readme_box code {
4123 4120 font-size: 12px !important;
4124 4121 background-color: ghostWhite !important;
4125 4122 color: #444 !important;
4126 4123 padding: 0 .2em !important;
4127 4124 border: 1px solid #dedede !important;
4128 4125 }
4129 4126
4130 4127 div.readme .readme_box pre code {
4131 4128 padding: 0 !important;
4132 4129 font-size: 12px !important;
4133 4130 background-color: #eee !important;
4134 4131 border: none !important;
4135 4132 }
4136 4133
4137 4134 div.readme .readme_box pre {
4138 4135 margin: 1em 0;
4139 4136 font-size: 12px;
4140 4137 background-color: #eee;
4141 4138 border: 1px solid #ddd;
4142 4139 padding: 5px;
4143 4140 color: #444;
4144 4141 overflow: auto;
4145 4142 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4146 4143 -webkit-border-radius: 3px;
4147 4144 -moz-border-radius: 3px;
4148 4145 border-radius: 3px;
4149 4146 }
4150 4147
4151 4148 div.readme .readme_box table {
4152 4149 display: table;
4153 4150 border-collapse: separate;
4154 4151 border-spacing: 2px;
4155 4152 border-color: gray;
4156 4153 width: auto !important;
4157 4154 }
4158 4155
4159 4156
4160 4157 /** RST STYLE **/
4161 4158
4162 4159
4163 4160 div.rst-block {
4164 4161 padding:0px;
4165 4162 }
4166 4163
4167 4164 div.rst-block h2 {
4168 4165 font-weight: normal;
4169 4166 }
4170 4167
4171 4168 div.rst-block {
4172 4169 background-color: #fafafa;
4173 4170 }
4174 4171
4175 4172 div.rst-block {
4176 4173 clear:both;
4177 4174 overflow:hidden;
4178 4175 margin:0;
4179 4176 padding:0 20px 10px;
4180 4177 }
4181 4178
4182 4179 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
4183 4180 border-bottom: 0 !important;
4184 4181 margin: 0 !important;
4185 4182 padding: 0 !important;
4186 4183 line-height: 1.5em !important;
4187 4184 }
4188 4185
4189 4186
4190 4187 div.rst-block h1:first-child {
4191 4188 padding-top: .25em !important;
4192 4189 }
4193 4190
4194 4191 div.rst-block h2, div.rst-block h3 {
4195 4192 margin: 1em 0 !important;
4196 4193 }
4197 4194
4198 4195 div.rst-block h2 {
4199 4196 margin-top: 1.5em !important;
4200 4197 border-top: 4px solid #e0e0e0 !important;
4201 4198 padding-top: .5em !important;
4202 4199 }
4203 4200
4204 4201 div.rst-block p {
4205 4202 color: black !important;
4206 4203 margin: 1em 0 !important;
4207 4204 line-height: 1.5em !important;
4208 4205 }
4209 4206
4210 4207 div.rst-block ul {
4211 4208 list-style: disc !important;
4212 4209 margin: 1em 0 1em 2em !important;
4213 4210 }
4214 4211
4215 4212 div.rst-block ol {
4216 4213 list-style: decimal;
4217 4214 margin: 1em 0 1em 2em !important;
4218 4215 }
4219 4216
4220 4217 div.rst-block pre, code {
4221 4218 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4222 4219 }
4223 4220
4224 4221 div.rst-block code {
4225 4222 font-size: 12px !important;
4226 4223 background-color: ghostWhite !important;
4227 4224 color: #444 !important;
4228 4225 padding: 0 .2em !important;
4229 4226 border: 1px solid #dedede !important;
4230 4227 }
4231 4228
4232 4229 div.rst-block pre code {
4233 4230 padding: 0 !important;
4234 4231 font-size: 12px !important;
4235 4232 background-color: #eee !important;
4236 4233 border: none !important;
4237 4234 }
4238 4235
4239 4236 div.rst-block pre {
4240 4237 margin: 1em 0;
4241 4238 font-size: 12px;
4242 4239 background-color: #eee;
4243 4240 border: 1px solid #ddd;
4244 4241 padding: 5px;
4245 4242 color: #444;
4246 4243 overflow: auto;
4247 4244 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4248 4245 -webkit-border-radius: 3px;
4249 4246 -moz-border-radius: 3px;
4250 4247 border-radius: 3px;
4251 4248 }
4252 4249
4253 4250
4254 4251 /** comment main **/
4255 4252 .comments {
4256 4253 padding:10px 20px;
4257 4254 }
4258 4255
4259 4256 .comments .comment {
4260 4257 border: 1px solid #ddd;
4261 4258 margin-top: 10px;
4262 4259 -webkit-border-radius: 4px;
4263 4260 -moz-border-radius: 4px;
4264 4261 border-radius: 4px;
4265 4262 }
4266 4263
4267 4264 .comments .comment .meta {
4268 4265 background: #f8f8f8;
4269 4266 padding: 4px;
4270 4267 border-bottom: 1px solid #ddd;
4271 4268 height: 18px;
4272 4269 }
4273 4270
4274 4271 .comments .comment .meta img {
4275 4272 vertical-align: middle;
4276 4273 }
4277 4274
4278 4275 .comments .comment .meta .user {
4279 4276 font-weight: bold;
4280 4277 float: left;
4281 4278 padding: 4px 2px 2px 2px;
4282 4279 }
4283 4280
4284 4281 .comments .comment .meta .date {
4285 4282 float: left;
4286 4283 padding:4px 4px 0px 4px;
4287 4284 }
4288 4285
4289 4286 .comments .comment .text {
4290 4287 background-color: #FAFAFA;
4291 4288 }
4292 4289 .comment .text div.rst-block p {
4293 4290 margin: 0.5em 0px !important;
4294 4291 }
4295 4292
4296 4293 .comments .comments-number{
4297 4294 padding:0px 0px 10px 0px;
4298 4295 font-weight: bold;
4299 4296 color: #666;
4300 4297 font-size: 16px;
4301 4298 }
4302 4299
4303 4300 /** comment form **/
4304 4301
4305 4302 .status-block{
4306 4303 height:80px;
4307 4304 clear:both
4308 4305 }
4309 4306
4310 4307 .comment-form .clearfix{
4311 4308 background: #EEE;
4312 4309 -webkit-border-radius: 4px;
4313 4310 -moz-border-radius: 4px;
4314 4311 border-radius: 4px;
4315 4312 padding: 10px;
4316 4313 }
4317 4314
4318 4315 div.comment-form {
4319 4316 margin-top: 20px;
4320 4317 }
4321 4318
4322 4319 .comment-form strong {
4323 4320 display: block;
4324 4321 margin-bottom: 15px;
4325 4322 }
4326 4323
4327 4324 .comment-form textarea {
4328 4325 width: 100%;
4329 4326 height: 100px;
4330 4327 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4331 4328 }
4332 4329
4333 4330 form.comment-form {
4334 4331 margin-top: 10px;
4335 4332 margin-left: 10px;
4336 4333 }
4337 4334
4338 4335 .comment-form-submit {
4339 4336 margin-top: 5px;
4340 4337 margin-left: 525px;
4341 4338 }
4342 4339
4343 4340 .file-comments {
4344 4341 display: none;
4345 4342 }
4346 4343
4347 4344 .comment-form .comment {
4348 4345 margin-left: 10px;
4349 4346 }
4350 4347
4351 4348 .comment-form .comment-help{
4352 4349 padding: 0px 0px 5px 0px;
4353 4350 color: #666;
4354 4351 }
4355 4352
4356 4353 .comment-form .comment-button{
4357 4354 padding-top:5px;
4358 4355 }
4359 4356
4360 4357 .add-another-button {
4361 4358 margin-left: 10px;
4362 4359 margin-top: 10px;
4363 4360 margin-bottom: 10px;
4364 4361 }
4365 4362
4366 4363 .comment .buttons {
4367 4364 float: right;
4368 4365 padding:2px 2px 0px 0px;
4369 4366 }
4370 4367
4371 4368
4372 4369 .show-inline-comments{
4373 4370 position: relative;
4374 4371 top:1px
4375 4372 }
4376 4373
4377 4374 /** comment inline form **/
4378 4375 .comment-inline-form .overlay{
4379 4376 display: none;
4380 4377 }
4381 4378 .comment-inline-form .overlay.submitting{
4382 4379 display:block;
4383 4380 background: none repeat scroll 0 0 white;
4384 4381 font-size: 16px;
4385 4382 opacity: 0.5;
4386 4383 position: absolute;
4387 4384 text-align: center;
4388 4385 vertical-align: top;
4389 4386
4390 4387 }
4391 4388 .comment-inline-form .overlay.submitting .overlay-text{
4392 4389 width:100%;
4393 4390 margin-top:5%;
4394 4391 }
4395 4392
4396 4393 .comment-inline-form .clearfix{
4397 4394 background: #EEE;
4398 4395 -webkit-border-radius: 4px;
4399 4396 -moz-border-radius: 4px;
4400 4397 border-radius: 4px;
4401 4398 padding: 5px;
4402 4399 }
4403 4400
4404 4401 div.comment-inline-form {
4405 4402 padding:4px 0px 6px 0px;
4406 4403 }
4407 4404
4408 4405
4409 4406 tr.hl-comment{
4410 4407 /*
4411 4408 background-color: #FFFFCC !important;
4412 4409 */
4413 4410 }
4414 4411
4415 4412 /*
4416 4413 tr.hl-comment pre {
4417 4414 border-top: 2px solid #FFEE33;
4418 4415 border-left: 2px solid #FFEE33;
4419 4416 border-right: 2px solid #FFEE33;
4420 4417 }
4421 4418 */
4422 4419
4423 4420 .comment-inline-form strong {
4424 4421 display: block;
4425 4422 margin-bottom: 15px;
4426 4423 }
4427 4424
4428 4425 .comment-inline-form textarea {
4429 4426 width: 100%;
4430 4427 height: 100px;
4431 4428 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4432 4429 }
4433 4430
4434 4431 form.comment-inline-form {
4435 4432 margin-top: 10px;
4436 4433 margin-left: 10px;
4437 4434 }
4438 4435
4439 4436 .comment-inline-form-submit {
4440 4437 margin-top: 5px;
4441 4438 margin-left: 525px;
4442 4439 }
4443 4440
4444 4441 .file-comments {
4445 4442 display: none;
4446 4443 }
4447 4444
4448 4445 .comment-inline-form .comment {
4449 4446 margin-left: 10px;
4450 4447 }
4451 4448
4452 4449 .comment-inline-form .comment-help{
4453 4450 padding: 0px 0px 2px 0px;
4454 4451 color: #666666;
4455 4452 font-size: 10px;
4456 4453 }
4457 4454
4458 4455 .comment-inline-form .comment-button{
4459 4456 padding-top:5px;
4460 4457 }
4461 4458
4462 4459 /** comment inline **/
4463 4460 .inline-comments {
4464 4461 padding:10px 20px;
4465 4462 }
4466 4463
4467 4464 .inline-comments div.rst-block {
4468 4465 clear:both;
4469 4466 overflow:hidden;
4470 4467 margin:0;
4471 4468 padding:0 20px 0px;
4472 4469 }
4473 4470 .inline-comments .comment {
4474 4471 border: 1px solid #ddd;
4475 4472 -webkit-border-radius: 4px;
4476 4473 -moz-border-radius: 4px;
4477 4474 border-radius: 4px;
4478 4475 margin: 3px 3px 5px 5px;
4479 4476 background-color: #FAFAFA;
4480 4477 }
4481 4478 .inline-comments .add-comment {
4482 4479 padding: 2px 4px 8px 5px;
4483 4480 }
4484 4481
4485 4482 .inline-comments .comment-wrapp{
4486 4483 padding:1px;
4487 4484 }
4488 4485 .inline-comments .comment .meta {
4489 4486 background: #f8f8f8;
4490 4487 padding: 4px;
4491 4488 border-bottom: 1px solid #ddd;
4492 4489 height: 20px;
4493 4490 }
4494 4491
4495 4492 .inline-comments .comment .meta img {
4496 4493 vertical-align: middle;
4497 4494 }
4498 4495
4499 4496 .inline-comments .comment .meta .user {
4500 4497 font-weight: bold;
4501 4498 float:left;
4502 4499 padding: 3px;
4503 4500 }
4504 4501
4505 4502 .inline-comments .comment .meta .date {
4506 4503 float:left;
4507 4504 padding: 3px;
4508 4505 }
4509 4506
4510 4507 .inline-comments .comment .text {
4511 4508 background-color: #FAFAFA;
4512 4509 }
4513 4510
4514 4511 .inline-comments .comments-number{
4515 4512 padding:0px 0px 10px 0px;
4516 4513 font-weight: bold;
4517 4514 color: #666;
4518 4515 font-size: 16px;
4519 4516 }
4520 4517 .inline-comments-button .add-comment{
4521 4518 margin:2px 0px 8px 5px !important
4522 4519 }
4523 4520
4524 4521
4525 4522 .notification-paginator{
4526 4523 padding: 0px 0px 4px 16px;
4527 4524 float: left;
4528 4525 }
4529 4526
4530 4527 .notifications{
4531 4528 border-radius: 4px 4px 4px 4px;
4532 4529 -webkit-border-radius: 4px;
4533 4530 -moz-border-radius: 4px;
4534 4531 float: right;
4535 4532 margin: 20px 0px 0px 0px;
4536 4533 position: absolute;
4537 4534 text-align: center;
4538 4535 width: 26px;
4539 4536 z-index: 1000;
4540 4537 }
4541 4538 .notifications a{
4542 4539 color:#888 !important;
4543 4540 display: block;
4544 4541 font-size: 10px;
4545 4542 background-color: #DEDEDE !important;
4546 4543 border-radius: 2px !important;
4547 4544 -webkit-border-radius: 2px !important;
4548 4545 -moz-border-radius: 2px !important;
4549 4546 }
4550 4547 .notifications a:hover{
4551 4548 text-decoration: none !important;
4552 4549 background-color: #EEEFFF !important;
4553 4550 }
4554 4551 .notification-header{
4555 4552 padding-top:6px;
4556 4553 }
4557 4554 .notification-header .desc{
4558 4555 font-size: 16px;
4559 4556 height: 24px;
4560 4557 float: left
4561 4558 }
4562 4559 .notification-list .container.unread{
4563 4560 background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
4564 4561 }
4565 4562 .notification-header .gravatar{
4566 4563 background: none repeat scroll 0 0 transparent;
4567 4564 padding: 0px 0px 0px 8px;
4568 4565 }
4569 4566 .notification-list .container .notification-header .desc{
4570 4567 font-weight: bold;
4571 4568 font-size: 17px;
4572 4569 }
4573 4570 .notification-table{
4574 4571 border: 1px solid #ccc;
4575 4572 -webkit-border-radius: 6px 6px 6px 6px;
4576 4573 -moz-border-radius: 6px 6px 6px 6px;
4577 4574 border-radius: 6px 6px 6px 6px;
4578 4575 clear: both;
4579 4576 margin: 0px 20px 0px 20px;
4580 4577 }
4581 4578 .notification-header .delete-notifications{
4582 4579 float: right;
4583 4580 padding-top: 8px;
4584 4581 cursor: pointer;
4585 4582 }
4586 4583 .notification-header .read-notifications{
4587 4584 float: right;
4588 4585 padding-top: 8px;
4589 4586 cursor: pointer;
4590 4587 }
4591 4588 .notification-subject{
4592 4589 clear:both;
4593 4590 border-bottom: 1px solid #eee;
4594 4591 padding:5px 0px 5px 38px;
4595 4592 }
4596 4593
4597 4594 .notification-body{
4598 4595 clear:both;
4599 4596 margin: 34px 2px 2px 8px
4600 4597 }
4601 4598
4602 4599 /****
4603 4600 PULL REQUESTS
4604 4601 *****/
4605 4602 .pullrequests_section_head {
4606 4603 padding:10px 10px 10px 0px;
4607 4604 font-size:16px;
4608 4605 font-weight: bold;
4609 4606 }
4610 4607
4611 4608 /****
4612 4609 PERMS
4613 4610 *****/
4614 4611 #perms .perms_section_head {
4615 4612 padding:10px 10px 10px 0px;
4616 4613 font-size:16px;
4617 4614 font-weight: bold;
4618 4615 }
4619 4616
4620 4617 #perms .perm_tag{
4621 4618 padding: 1px 3px 1px 3px;
4622 4619 font-size: 10px;
4623 4620 font-weight: bold;
4624 4621 text-transform: uppercase;
4625 4622 white-space: nowrap;
4626 4623 -webkit-border-radius: 3px;
4627 4624 -moz-border-radius: 3px;
4628 4625 border-radius: 3px;
4629 4626 }
4630 4627
4631 4628 #perms .perm_tag.admin{
4632 4629 background-color: #B94A48;
4633 4630 color: #ffffff;
4634 4631 }
4635 4632
4636 4633 #perms .perm_tag.write{
4637 4634 background-color: #B94A48;
4638 4635 color: #ffffff;
4639 4636 }
4640 4637
4641 4638 #perms .perm_tag.read{
4642 4639 background-color: #468847;
4643 4640 color: #ffffff;
4644 4641 }
4645 4642
4646 4643 #perms .perm_tag.none{
4647 4644 background-color: #bfbfbf;
4648 4645 color: #ffffff;
4649 4646 }
4650 4647
4651 4648 .perm-gravatar{
4652 4649 vertical-align:middle;
4653 4650 padding:2px;
4654 4651 }
4655 4652 .perm-gravatar-ac{
4656 4653 vertical-align:middle;
4657 4654 padding:2px;
4658 4655 width: 14px;
4659 4656 height: 14px;
4660 4657 }
4661 4658
4662 4659 /*****************************************************************************
4663 4660 DIFFS CSS
4664 4661 ******************************************************************************/
4665 4662
4666 4663 div.diffblock {
4667 4664 overflow: auto;
4668 4665 padding: 0px;
4669 4666 border: 1px solid #ccc;
4670 4667 background: #f8f8f8;
4671 4668 font-size: 100%;
4672 4669 line-height: 100%;
4673 4670 /* new */
4674 4671 line-height: 125%;
4675 4672 -webkit-border-radius: 6px 6px 0px 0px;
4676 4673 -moz-border-radius: 6px 6px 0px 0px;
4677 4674 border-radius: 6px 6px 0px 0px;
4678 4675 }
4679 4676 div.diffblock.margined{
4680 4677 margin: 0px 20px 0px 20px;
4681 4678 }
4682 4679 div.diffblock .code-header{
4683 4680 border-bottom: 1px solid #CCCCCC;
4684 4681 background: #EEEEEE;
4685 4682 padding:10px 0 10px 0;
4686 4683 height: 14px;
4687 4684 }
4688 4685
4689 4686 div.diffblock .code-header.banner{
4690 4687 border-bottom: 1px solid #CCCCCC;
4691 4688 background: #EEEEEE;
4692 4689 height: 14px;
4693 4690 margin: 0px 95px 0px 95px;
4694 4691 padding: 3px 3px 11px 3px;
4695 4692 }
4696 4693
4697 4694 div.diffblock .code-header.cv{
4698 4695 height: 34px;
4699 4696 }
4700 4697 div.diffblock .code-header-title{
4701 4698 padding: 0px 0px 10px 5px !important;
4702 4699 margin: 0 !important;
4703 4700 }
4704 4701 div.diffblock .code-header .hash{
4705 4702 float: left;
4706 4703 padding: 2px 0 0 2px;
4707 4704 }
4708 4705 div.diffblock .code-header .date{
4709 4706 float:left;
4710 4707 text-transform: uppercase;
4711 4708 padding: 2px 0px 0px 2px;
4712 4709 }
4713 4710 div.diffblock .code-header div{
4714 4711 margin-left:4px;
4715 4712 font-weight: bold;
4716 4713 font-size: 14px;
4717 4714 }
4718 4715
4719 4716 div.diffblock .parents {
4720 4717 float: left;
4721 4718 height: 26px;
4722 4719 width:100px;
4723 4720 font-size: 10px;
4724 4721 font-weight: 400;
4725 4722 vertical-align: middle;
4726 4723 padding: 0px 2px 2px 2px;
4727 4724 background-color:#eeeeee;
4728 4725 border-bottom: 1px solid #CCCCCC;
4729 4726 }
4730 4727
4731 4728 div.diffblock .children {
4732 4729 float: right;
4733 4730 height: 26px;
4734 4731 width:100px;
4735 4732 font-size: 10px;
4736 4733 font-weight: 400;
4737 4734 vertical-align: middle;
4738 4735 text-align: right;
4739 4736 padding: 0px 2px 2px 2px;
4740 4737 background-color:#eeeeee;
4741 4738 border-bottom: 1px solid #CCCCCC;
4742 4739 }
4743 4740
4744 4741 div.diffblock .code-body{
4745 4742 background: #FFFFFF;
4746 4743 }
4747 4744 div.diffblock pre.raw{
4748 4745 background: #FFFFFF;
4749 4746 color:#000000;
4750 4747 }
4751 4748 table.code-difftable{
4752 4749 border-collapse: collapse;
4753 4750 width: 99%;
4754 4751 }
4755 4752 table.code-difftable td {
4756 4753 padding: 0 !important;
4757 4754 background: none !important;
4758 4755 border:0 !important;
4759 4756 vertical-align: none !important;
4760 4757 }
4761 4758 table.code-difftable .context{
4762 4759 background:none repeat scroll 0 0 #DDE7EF;
4763 4760 }
4764 4761 table.code-difftable .add{
4765 4762 background:none repeat scroll 0 0 #DDFFDD;
4766 4763 }
4767 4764 table.code-difftable .add ins{
4768 4765 background:none repeat scroll 0 0 #AAFFAA;
4769 4766 text-decoration:none;
4770 4767 }
4771 4768 table.code-difftable .del{
4772 4769 background:none repeat scroll 0 0 #FFDDDD;
4773 4770 }
4774 4771 table.code-difftable .del del{
4775 4772 background:none repeat scroll 0 0 #FFAAAA;
4776 4773 text-decoration:none;
4777 4774 }
4778 4775
4779 4776 /** LINE NUMBERS **/
4780 4777 table.code-difftable .lineno{
4781 4778
4782 4779 padding-left:2px;
4783 4780 padding-right:2px;
4784 4781 text-align:right;
4785 4782 width:32px;
4786 4783 -moz-user-select:none;
4787 4784 -webkit-user-select: none;
4788 4785 border-right: 1px solid #CCC !important;
4789 4786 border-left: 0px solid #CCC !important;
4790 4787 border-top: 0px solid #CCC !important;
4791 4788 border-bottom: none !important;
4792 4789 vertical-align: middle !important;
4793 4790
4794 4791 }
4795 4792 table.code-difftable .lineno.new {
4796 4793 }
4797 4794 table.code-difftable .lineno.old {
4798 4795 }
4799 4796 table.code-difftable .lineno a{
4800 4797 color:#747474 !important;
4801 4798 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4802 4799 letter-spacing:-1px;
4803 4800 text-align:right;
4804 4801 padding-right: 2px;
4805 4802 cursor: pointer;
4806 4803 display: block;
4807 4804 width: 32px;
4808 4805 }
4809 4806
4810 4807 table.code-difftable .lineno-inline{
4811 4808 background:none repeat scroll 0 0 #FFF !important;
4812 4809 padding-left:2px;
4813 4810 padding-right:2px;
4814 4811 text-align:right;
4815 4812 width:30px;
4816 4813 -moz-user-select:none;
4817 4814 -webkit-user-select: none;
4818 4815 }
4819 4816
4820 4817 /** CODE **/
4821 4818 table.code-difftable .code {
4822 4819 display: block;
4823 4820 width: 100%;
4824 4821 }
4825 4822 table.code-difftable .code td{
4826 4823 margin:0;
4827 4824 padding:0;
4828 4825 }
4829 4826 table.code-difftable .code pre{
4830 4827 margin:0;
4831 4828 padding:0;
4832 4829 height: 17px;
4833 4830 line-height: 17px;
4834 4831 }
4835 4832
4836 4833
4837 4834 .diffblock.margined.comm .line .code:hover{
4838 4835 background-color:#FFFFCC !important;
4839 4836 cursor: pointer !important;
4840 4837 background-image:url("../images/icons/comment_add.png") !important;
4841 4838 background-repeat:no-repeat !important;
4842 4839 background-position: right !important;
4843 4840 background-position: 0% 50% !important;
4844 4841 }
4845 4842 .diffblock.margined.comm .line .code.no-comment:hover{
4846 4843 background-image: none !important;
4847 4844 cursor: auto !important;
4848 4845 background-color: inherit !important;
4849 4846
4850 4847 }
General Comments 0
You need to be logged in to leave comments. Login now