##// END OF EJS Templates
merge
marcink -
r1419:febb3f95 merge beta
parent child Browse files
Show More
1 NO CONTENT: new file 100644
@@ -1,395 +1,399 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10 from rhodecode.lib.utils import check_repo_fast as cr
11 11
12 12 # prefix for non repository related links needs to be prefixed with `/`
13 13 ADMIN_PREFIX = '/_admin'
14 14
15 15
16 16 def make_map(config):
17 17 """Create, configure and return the routes Mapper"""
18 18 rmap = Mapper(directory=config['pylons.paths']['controllers'],
19 19 always_scan=config['debug'])
20 20 rmap.minimization = False
21 21 rmap.explicit = False
22 22
23 23 def check_repo(environ, match_dict):
24 24 """
25 25 check for valid repository for proper 404 handling
26 26 :param environ:
27 27 :param match_dict:
28 28 """
29 29 repo_name = match_dict.get('repo_name')
30 30 return not cr(repo_name, config['base_path'])
31 31
32 32
33 33 def check_int(environ, match_dict):
34 34 return match_dict.get('id').isdigit()
35 35
36 36
37 37
38 38
39 39 # The ErrorController route (handles 404/500 error pages); it should
40 40 # likely stay at the top, ensuring it can always be resolved
41 41 rmap.connect('/error/{action}', controller='error')
42 42 rmap.connect('/error/{action}/{id}', controller='error')
43 43
44 44 #==========================================================================
45 45 # CUSTOM ROUTES HERE
46 46 #==========================================================================
47 47
48 48 #MAIN PAGE
49 49 rmap.connect('home', '/', controller='home', action='index')
50 50 rmap.connect('repo_switcher', '/repos', controller='home',
51 51 action='repo_switcher')
52 52 rmap.connect('bugtracker',
53 53 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
54 54 _static=True)
55 55 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
56 56
57 57 #ADMIN REPOSITORY REST ROUTES
58 58 with rmap.submapper(path_prefix=ADMIN_PREFIX,
59 59 controller='admin/repos') as m:
60 60 m.connect("repos", "/repos",
61 61 action="create", conditions=dict(method=["POST"]))
62 62 m.connect("repos", "/repos",
63 63 action="index", conditions=dict(method=["GET"]))
64 64 m.connect("formatted_repos", "/repos.{format}",
65 65 action="index",
66 66 conditions=dict(method=["GET"]))
67 67 m.connect("new_repo", "/repos/new",
68 68 action="new", conditions=dict(method=["GET"]))
69 69 m.connect("formatted_new_repo", "/repos/new.{format}",
70 70 action="new", conditions=dict(method=["GET"]))
71 71 m.connect("/repos/{repo_name:.*}",
72 72 action="update", conditions=dict(method=["PUT"],
73 73 function=check_repo))
74 74 m.connect("/repos/{repo_name:.*}",
75 75 action="delete", conditions=dict(method=["DELETE"],
76 76 function=check_repo))
77 77 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
78 78 action="edit", conditions=dict(method=["GET"],
79 79 function=check_repo))
80 80 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
81 81 action="edit", conditions=dict(method=["GET"],
82 82 function=check_repo))
83 83 m.connect("repo", "/repos/{repo_name:.*}",
84 84 action="show", conditions=dict(method=["GET"],
85 85 function=check_repo))
86 86 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
87 87 action="show", conditions=dict(method=["GET"],
88 88 function=check_repo))
89 89 #ajax delete repo perm user
90 90 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
91 91 action="delete_perm_user", conditions=dict(method=["DELETE"],
92 92 function=check_repo))
93 93 #ajax delete repo perm users_group
94 94 m.connect('delete_repo_users_group',
95 95 "/repos_delete_users_group/{repo_name:.*}",
96 96 action="delete_perm_users_group",
97 97 conditions=dict(method=["DELETE"], function=check_repo))
98 98
99 99 #settings actions
100 100 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
101 101 action="repo_stats", conditions=dict(method=["DELETE"],
102 102 function=check_repo))
103 103 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
104 104 action="repo_cache", conditions=dict(method=["DELETE"],
105 105 function=check_repo))
106 106 m.connect('repo_public_journal',
107 107 "/repos_public_journal/{repo_name:.*}",
108 108 action="repo_public_journal", conditions=dict(method=["PUT"],
109 109 function=check_repo))
110 110 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
111 111 action="repo_pull", conditions=dict(method=["PUT"],
112 112 function=check_repo))
113 113
114 114 with rmap.submapper(path_prefix=ADMIN_PREFIX,
115 115 controller='admin/repos_groups') as m:
116 116 m.connect("repos_groups", "/repos_groups",
117 117 action="create", conditions=dict(method=["POST"]))
118 118 m.connect("repos_groups", "/repos_groups",
119 119 action="index", conditions=dict(method=["GET"]))
120 120 m.connect("formatted_repos_groups", "/repos_groups.{format}",
121 121 action="index", conditions=dict(method=["GET"]))
122 122 m.connect("new_repos_group", "/repos_groups/new",
123 123 action="new", conditions=dict(method=["GET"]))
124 124 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
125 125 action="new", conditions=dict(method=["GET"]))
126 126 m.connect("update_repos_group", "/repos_groups/{id}",
127 127 action="update", conditions=dict(method=["PUT"],
128 128 function=check_int))
129 129 m.connect("delete_repos_group", "/repos_groups/{id}",
130 130 action="delete", conditions=dict(method=["DELETE"],
131 131 function=check_int))
132 132 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
133 133 action="edit", conditions=dict(method=["GET"],
134 134 function=check_int))
135 135 m.connect("formatted_edit_repos_group",
136 136 "/repos_groups/{id}.{format}/edit",
137 137 action="edit", conditions=dict(method=["GET"],
138 138 function=check_int))
139 139 m.connect("repos_group", "/repos_groups/{id}",
140 140 action="show", conditions=dict(method=["GET"],
141 141 function=check_int))
142 142 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
143 143 action="show", conditions=dict(method=["GET"],
144 144 function=check_int))
145 145
146 146 #ADMIN USER REST ROUTES
147 147 with rmap.submapper(path_prefix=ADMIN_PREFIX,
148 148 controller='admin/users') as m:
149 149 m.connect("users", "/users",
150 150 action="create", conditions=dict(method=["POST"]))
151 151 m.connect("users", "/users",
152 152 action="index", conditions=dict(method=["GET"]))
153 153 m.connect("formatted_users", "/users.{format}",
154 154 action="index", conditions=dict(method=["GET"]))
155 155 m.connect("new_user", "/users/new",
156 156 action="new", conditions=dict(method=["GET"]))
157 157 m.connect("formatted_new_user", "/users/new.{format}",
158 158 action="new", conditions=dict(method=["GET"]))
159 159 m.connect("update_user", "/users/{id}",
160 160 action="update", conditions=dict(method=["PUT"]))
161 161 m.connect("delete_user", "/users/{id}",
162 162 action="delete", conditions=dict(method=["DELETE"]))
163 163 m.connect("edit_user", "/users/{id}/edit",
164 164 action="edit", conditions=dict(method=["GET"]))
165 165 m.connect("formatted_edit_user",
166 166 "/users/{id}.{format}/edit",
167 167 action="edit", conditions=dict(method=["GET"]))
168 168 m.connect("user", "/users/{id}",
169 169 action="show", conditions=dict(method=["GET"]))
170 170 m.connect("formatted_user", "/users/{id}.{format}",
171 171 action="show", conditions=dict(method=["GET"]))
172 172
173 173 #EXTRAS USER ROUTES
174 174 m.connect("user_perm", "/users_perm/{id}",
175 175 action="update_perm", conditions=dict(method=["PUT"]))
176 176
177 177 #ADMIN USERS REST ROUTES
178 178 with rmap.submapper(path_prefix=ADMIN_PREFIX,
179 179 controller='admin/users_groups') as m:
180 180 m.connect("users_groups", "/users_groups",
181 181 action="create", conditions=dict(method=["POST"]))
182 182 m.connect("users_groups", "/users_groups",
183 183 action="index", conditions=dict(method=["GET"]))
184 184 m.connect("formatted_users_groups", "/users_groups.{format}",
185 185 action="index", conditions=dict(method=["GET"]))
186 186 m.connect("new_users_group", "/users_groups/new",
187 187 action="new", conditions=dict(method=["GET"]))
188 188 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
189 189 action="new", conditions=dict(method=["GET"]))
190 190 m.connect("update_users_group", "/users_groups/{id}",
191 191 action="update", conditions=dict(method=["PUT"]))
192 192 m.connect("delete_users_group", "/users_groups/{id}",
193 193 action="delete", conditions=dict(method=["DELETE"]))
194 194 m.connect("edit_users_group", "/users_groups/{id}/edit",
195 195 action="edit", conditions=dict(method=["GET"]))
196 196 m.connect("formatted_edit_users_group",
197 197 "/users_groups/{id}.{format}/edit",
198 198 action="edit", conditions=dict(method=["GET"]))
199 199 m.connect("users_group", "/users_groups/{id}",
200 200 action="show", conditions=dict(method=["GET"]))
201 201 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
202 202 action="show", conditions=dict(method=["GET"]))
203 203
204 204 #EXTRAS USER ROUTES
205 205 m.connect("users_group_perm", "/users_groups_perm/{id}",
206 206 action="update_perm", conditions=dict(method=["PUT"]))
207 207
208 208 #ADMIN GROUP REST ROUTES
209 209 rmap.resource('group', 'groups',
210 210 controller='admin/groups', path_prefix=ADMIN_PREFIX)
211 211
212 212 #ADMIN PERMISSIONS REST ROUTES
213 213 rmap.resource('permission', 'permissions',
214 214 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
215 215
216 216 ##ADMIN LDAP SETTINGS
217 217 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
218 218 controller='admin/ldap_settings', action='ldap_settings',
219 219 conditions=dict(method=["POST"]))
220 220
221 221 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
222 222 controller='admin/ldap_settings')
223 223
224 224 #ADMIN SETTINGS REST ROUTES
225 225 with rmap.submapper(path_prefix=ADMIN_PREFIX,
226 226 controller='admin/settings') as m:
227 227 m.connect("admin_settings", "/settings",
228 228 action="create", conditions=dict(method=["POST"]))
229 229 m.connect("admin_settings", "/settings",
230 230 action="index", conditions=dict(method=["GET"]))
231 231 m.connect("formatted_admin_settings", "/settings.{format}",
232 232 action="index", conditions=dict(method=["GET"]))
233 233 m.connect("admin_new_setting", "/settings/new",
234 234 action="new", conditions=dict(method=["GET"]))
235 235 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
236 236 action="new", conditions=dict(method=["GET"]))
237 237 m.connect("/settings/{setting_id}",
238 238 action="update", conditions=dict(method=["PUT"]))
239 239 m.connect("/settings/{setting_id}",
240 240 action="delete", conditions=dict(method=["DELETE"]))
241 241 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
242 242 action="edit", conditions=dict(method=["GET"]))
243 243 m.connect("formatted_admin_edit_setting",
244 244 "/settings/{setting_id}.{format}/edit",
245 245 action="edit", conditions=dict(method=["GET"]))
246 246 m.connect("admin_setting", "/settings/{setting_id}",
247 247 action="show", conditions=dict(method=["GET"]))
248 248 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
249 249 action="show", conditions=dict(method=["GET"]))
250 250 m.connect("admin_settings_my_account", "/my_account",
251 251 action="my_account", conditions=dict(method=["GET"]))
252 252 m.connect("admin_settings_my_account_update", "/my_account_update",
253 253 action="my_account_update", conditions=dict(method=["PUT"]))
254 254 m.connect("admin_settings_create_repository", "/create_repository",
255 255 action="create_repository", conditions=dict(method=["GET"]))
256 256
257 257 #ADMIN MAIN PAGES
258 258 with rmap.submapper(path_prefix=ADMIN_PREFIX,
259 259 controller='admin/admin') as m:
260 260 m.connect('admin_home', '', action='index')
261 261 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
262 262 action='add_repo')
263 263
264 264 #USER JOURNAL
265 265 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
266 266
267 267 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
268 268 controller='journal', action="public_journal")
269 269
270 270 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
271 271 controller='journal', action="public_journal_rss")
272 272
273 273 rmap.connect('public_journal_atom',
274 274 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
275 275 action="public_journal_atom")
276 276
277 277 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
278 278 controller='journal', action='toggle_following',
279 279 conditions=dict(method=["POST"]))
280 280
281 281 #SEARCH
282 282 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
283 283 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
284 284 controller='search')
285 285
286 286 #LOGIN/LOGOUT/REGISTER/SIGN IN
287 287 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
288 288 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
289 289 action='logout')
290 290
291 291 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
292 292 action='register')
293 293
294 294 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
295 295 controller='login', action='password_reset')
296 296
297 rmap.connect('reset_password_confirmation',
298 '%s/password_reset_confirmation' % ADMIN_PREFIX,
299 controller='login', action='password_reset_confirmation')
300
297 301 #FEEDS
298 302 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
299 303 controller='feed', action='rss',
300 304 conditions=dict(function=check_repo))
301 305
302 306 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
303 307 controller='feed', action='atom',
304 308 conditions=dict(function=check_repo))
305 309
306 310 #==========================================================================
307 311 # REPOSITORY ROUTES
308 312 #==========================================================================
309 313 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
310 314 controller='changeset', revision='tip',
311 315 conditions=dict(function=check_repo))
312 316
313 317 rmap.connect('raw_changeset_home',
314 318 '/{repo_name:.*}/raw-changeset/{revision}',
315 319 controller='changeset', action='raw_changeset',
316 320 revision='tip', conditions=dict(function=check_repo))
317 321
318 322 rmap.connect('summary_home', '/{repo_name:.*}',
319 323 controller='summary', conditions=dict(function=check_repo))
320 324
321 325 rmap.connect('summary_home', '/{repo_name:.*}/summary',
322 326 controller='summary', conditions=dict(function=check_repo))
323 327
324 328 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
325 329 controller='shortlog', conditions=dict(function=check_repo))
326 330
327 331 rmap.connect('branches_home', '/{repo_name:.*}/branches',
328 332 controller='branches', conditions=dict(function=check_repo))
329 333
330 334 rmap.connect('tags_home', '/{repo_name:.*}/tags',
331 335 controller='tags', conditions=dict(function=check_repo))
332 336
333 337 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
334 338 controller='changelog', conditions=dict(function=check_repo))
335 339
336 340 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
337 341 controller='files', revision='tip', f_path='',
338 342 conditions=dict(function=check_repo))
339 343
340 344 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
341 345 controller='files', action='diff', revision='tip', f_path='',
342 346 conditions=dict(function=check_repo))
343 347
344 348 rmap.connect('files_rawfile_home',
345 349 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
346 350 controller='files', action='rawfile', revision='tip',
347 351 f_path='', conditions=dict(function=check_repo))
348 352
349 353 rmap.connect('files_raw_home',
350 354 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
351 355 controller='files', action='raw', revision='tip', f_path='',
352 356 conditions=dict(function=check_repo))
353 357
354 358 rmap.connect('files_annotate_home',
355 359 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
356 360 controller='files', action='annotate', revision='tip',
357 361 f_path='', conditions=dict(function=check_repo))
358 362
359 363 rmap.connect('files_edit_home',
360 364 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
361 365 controller='files', action='edit', revision='tip',
362 366 f_path='', conditions=dict(function=check_repo))
363 367
364 368 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
365 369 controller='files', action='archivefile',
366 370 conditions=dict(function=check_repo))
367 371
368 372 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
369 373 controller='settings', action="delete",
370 374 conditions=dict(method=["DELETE"], function=check_repo))
371 375
372 376 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
373 377 controller='settings', action="update",
374 378 conditions=dict(method=["PUT"], function=check_repo))
375 379
376 380 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
377 381 controller='settings', action='index',
378 382 conditions=dict(function=check_repo))
379 383
380 384 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
381 385 controller='settings', action='fork_create',
382 386 conditions=dict(function=check_repo, method=["POST"]))
383 387
384 388 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
385 389 controller='settings', action='fork',
386 390 conditions=dict(function=check_repo))
387 391
388 392 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
389 393 controller='followers', action='followers',
390 394 conditions=dict(function=check_repo))
391 395
392 396 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
393 397 controller='forks', action='forks',
394 398 conditions=dict(function=check_repo))
395 399 return rmap
@@ -1,151 +1,168 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) 2009-2011 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
29 29 from formencode import htmlfill
30 30
31 31 from pylons.i18n.translation import _
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34
35 35 import rhodecode.lib.helpers as h
36 36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
37 37 from rhodecode.lib.base import BaseController, render
38 38 from rhodecode.model.db import User
39 39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 40 from rhodecode.model.user import UserModel
41 41
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class LoginController(BaseController):
47 47
48 48 def __before__(self):
49 49 super(LoginController, self).__before__()
50 50
51 51 def index(self):
52 52 #redirect if already logged in
53 53 c.came_from = request.GET.get('came_from', None)
54 54
55 55 if self.rhodecode_user.is_authenticated \
56 56 and self.rhodecode_user.username != 'default':
57 57
58 58 return redirect(url('home'))
59 59
60 60 if request.POST:
61 61 #import Login Form validator class
62 62 login_form = LoginForm()
63 63 try:
64 64 c.form_result = login_form.to_python(dict(request.POST))
65 65 #form checks for username/password, now we're authenticated
66 66 username = c.form_result['username']
67 67 user = User.by_username(username,
68 68 case_insensitive=True)
69 69 auth_user = AuthUser(user.user_id)
70 70 auth_user.set_authenticated()
71 71 session['rhodecode_user'] = auth_user
72 72 session.save()
73 73
74 74 log.info('user %s is now authenticated and stored in session',
75 75 username)
76 76 user.update_lastlogin()
77 77
78 78 if c.came_from:
79 79 return redirect(c.came_from)
80 80 else:
81 81 return redirect(url('home'))
82 82
83 83 except formencode.Invalid, errors:
84 84 return htmlfill.render(
85 85 render('/login.html'),
86 86 defaults=errors.value,
87 87 errors=errors.error_dict or {},
88 88 prefix_error=False,
89 89 encoding="UTF-8")
90 90
91 91 return render('/login.html')
92 92
93 93 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
94 94 'hg.register.manual_activate')
95 95 def register(self):
96 96 user_model = UserModel()
97 97 c.auto_active = False
98 98 for perm in user_model.get_by_username('default',
99 99 cache=False).user_perms:
100 100 if perm.permission.permission_name == 'hg.register.auto_activate':
101 101 c.auto_active = True
102 102 break
103 103
104 104 if request.POST:
105 105
106 106 register_form = RegisterForm()()
107 107 try:
108 108 form_result = register_form.to_python(dict(request.POST))
109 109 form_result['active'] = c.auto_active
110 110 user_model.create_registration(form_result)
111 111 h.flash(_('You have successfully registered into rhodecode'),
112 112 category='success')
113 113 return redirect(url('login_home'))
114 114
115 115 except formencode.Invalid, errors:
116 116 return htmlfill.render(
117 117 render('/register.html'),
118 118 defaults=errors.value,
119 119 errors=errors.error_dict or {},
120 120 prefix_error=False,
121 121 encoding="UTF-8")
122 122
123 123 return render('/register.html')
124 124
125 125 def password_reset(self):
126 126 user_model = UserModel()
127 127 if request.POST:
128 128
129 129 password_reset_form = PasswordResetForm()()
130 130 try:
131 131 form_result = password_reset_form.to_python(dict(request.POST))
132 user_model.reset_password(form_result)
133 h.flash(_('Your new password was sent'),
132 user_model.reset_password_link(form_result)
133 h.flash(_('Your password reset link was sent'),
134 134 category='success')
135 135 return redirect(url('login_home'))
136 136
137 137 except formencode.Invalid, errors:
138 138 return htmlfill.render(
139 139 render('/password_reset.html'),
140 140 defaults=errors.value,
141 141 errors=errors.error_dict or {},
142 142 prefix_error=False,
143 143 encoding="UTF-8")
144 144
145 145 return render('/password_reset.html')
146 146
147 def password_reset_confirmation(self):
148
149 if request.GET and request.GET.get('key'):
150 try:
151 user_model = UserModel()
152 user = User.get_by_api_key(request.GET.get('key'))
153 data = dict(email=user.email)
154 user_model.reset_password(data)
155 h.flash(_('Your password reset was successful, '
156 'new password has been sent to your email'),
157 category='success')
158 except Exception, e:
159 log.error(e)
160 return redirect(url('reset_password'))
161
162 return redirect(url('login_home'))
163
147 164 def logout(self):
148 165 del session['rhodecode_user']
149 166 session.save()
150 167 log.info('Logging out and setting user as Empty')
151 168 redirect(url('home'))
@@ -1,374 +1,413 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.celerylib.tasks
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode task modules, containing all task that suppose to be run
7 7 by celery daemon
8 8
9 9 :created_on: Oct 6, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 from celery.decorators import task
27 27
28 28 import os
29 29 import traceback
30 30 import logging
31 31 from os.path import dirname as dn, join as jn
32 32
33 33 from time import mktime
34 34 from operator import itemgetter
35 35 from string import lower
36 36
37 from pylons import config
37 from pylons import config, url
38 38 from pylons.i18n.translation import _
39 39
40 40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 42 __get_lockkey, LockHeld, DaemonLock
43 43 from rhodecode.lib.helpers import person
44 44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 45 from rhodecode.lib.utils import add_cache
46 46 from rhodecode.lib.odict import OrderedDict
47 47 from rhodecode.model import init_model
48 48 from rhodecode.model import meta
49 49 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50 50
51 51 from vcs.backends import get_repo
52 52
53 53 from sqlalchemy import engine_from_config
54 54
55 55 add_cache(config)
56 56
57 57 try:
58 58 import json
59 59 except ImportError:
60 60 #python 2.5 compatibility
61 61 import simplejson as json
62 62
63 63 __all__ = ['whoosh_index', 'get_commits_stats',
64 64 'reset_user_password', 'send_email']
65 65
66 66 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
67 67
68 68
69 69 def get_session():
70 70 if CELERY_ON:
71 71 engine = engine_from_config(config, 'sqlalchemy.db1.')
72 72 init_model(engine)
73 73 sa = meta.Session()
74 74 return sa
75 75
76 76
77 77 def get_repos_path():
78 78 sa = get_session()
79 79 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
80 80 return q.ui_value
81 81
82 82
83 83 @task(ignore_result=True)
84 84 @locked_task
85 85 def whoosh_index(repo_location, full_index):
86 86 #log = whoosh_index.get_logger()
87 87 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
88 88 index_location = config['index_dir']
89 89 WhooshIndexingDaemon(index_location=index_location,
90 90 repo_location=repo_location, sa=get_session())\
91 91 .run(full_index=full_index)
92 92
93 93
94 94 @task(ignore_result=True)
95 95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 96 try:
97 97 log = get_commits_stats.get_logger()
98 98 except:
99 99 log = logging.getLogger(__name__)
100 100
101 101 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
102 102 ts_max_y)
103 103 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
104 104 log.info('running task with lockkey %s', lockkey)
105 105 try:
106 106 lock = l = DaemonLock(jn(lockkey_path, lockkey))
107 107
108 108 #for js data compatibilty cleans the key for person from '
109 109 akc = lambda k: person(k).replace('"', "")
110 110
111 111 co_day_auth_aggr = {}
112 112 commits_by_day_aggregate = {}
113 113 repos_path = get_repos_path()
114 114 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
115 115 repo_size = len(repo.revisions)
116 116 #return if repo have no revisions
117 117 if repo_size < 1:
118 118 lock.release()
119 119 return True
120 120
121 121 skip_date_limit = True
122 122 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
123 123 last_rev = 0
124 124 last_cs = None
125 125 timegetter = itemgetter('time')
126 126
127 127 sa = get_session()
128 128
129 129 dbrepo = sa.query(Repository)\
130 130 .filter(Repository.repo_name == repo_name).scalar()
131 131 cur_stats = sa.query(Statistics)\
132 132 .filter(Statistics.repository == dbrepo).scalar()
133 133
134 134 if cur_stats is not None:
135 135 last_rev = cur_stats.stat_on_revision
136 136
137 137 if last_rev == repo.get_changeset().revision and repo_size > 1:
138 138 #pass silently without any work if we're not on first revision or
139 139 #current state of parsing revision(from db marker) is the
140 140 #last revision
141 141 lock.release()
142 142 return True
143 143
144 144 if cur_stats:
145 145 commits_by_day_aggregate = OrderedDict(json.loads(
146 146 cur_stats.commit_activity_combined))
147 147 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
148 148
149 149 log.debug('starting parsing %s', parse_limit)
150 150 lmktime = mktime
151 151
152 152 last_rev = last_rev + 1 if last_rev > 0 else last_rev
153 153
154 154 for cs in repo[last_rev:last_rev + parse_limit]:
155 155 last_cs = cs # remember last parsed changeset
156 156 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
157 157 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
158 158
159 159 if akc(cs.author) in co_day_auth_aggr:
160 160 try:
161 161 l = [timegetter(x) for x in
162 162 co_day_auth_aggr[akc(cs.author)]['data']]
163 163 time_pos = l.index(k)
164 164 except ValueError:
165 165 time_pos = False
166 166
167 167 if time_pos >= 0 and time_pos is not False:
168 168
169 169 datadict = \
170 170 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
171 171
172 172 datadict["commits"] += 1
173 173 datadict["added"] += len(cs.added)
174 174 datadict["changed"] += len(cs.changed)
175 175 datadict["removed"] += len(cs.removed)
176 176
177 177 else:
178 178 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
179 179
180 180 datadict = {"time": k,
181 181 "commits": 1,
182 182 "added": len(cs.added),
183 183 "changed": len(cs.changed),
184 184 "removed": len(cs.removed),
185 185 }
186 186 co_day_auth_aggr[akc(cs.author)]['data']\
187 187 .append(datadict)
188 188
189 189 else:
190 190 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
191 191 co_day_auth_aggr[akc(cs.author)] = {
192 192 "label": akc(cs.author),
193 193 "data": [{"time":k,
194 194 "commits":1,
195 195 "added":len(cs.added),
196 196 "changed":len(cs.changed),
197 197 "removed":len(cs.removed),
198 198 }],
199 199 "schema": ["commits"],
200 200 }
201 201
202 202 #gather all data by day
203 203 if k in commits_by_day_aggregate:
204 204 commits_by_day_aggregate[k] += 1
205 205 else:
206 206 commits_by_day_aggregate[k] = 1
207 207
208 208 overview_data = sorted(commits_by_day_aggregate.items(),
209 209 key=itemgetter(0))
210 210
211 211 if not co_day_auth_aggr:
212 212 co_day_auth_aggr[akc(repo.contact)] = {
213 213 "label": akc(repo.contact),
214 214 "data": [0, 1],
215 215 "schema": ["commits"],
216 216 }
217 217
218 218 stats = cur_stats if cur_stats else Statistics()
219 219 stats.commit_activity = json.dumps(co_day_auth_aggr)
220 220 stats.commit_activity_combined = json.dumps(overview_data)
221 221
222 222 log.debug('last revison %s', last_rev)
223 223 leftovers = len(repo.revisions[last_rev:])
224 224 log.debug('revisions to parse %s', leftovers)
225 225
226 226 if last_rev == 0 or leftovers < parse_limit:
227 227 log.debug('getting code trending stats')
228 228 stats.languages = json.dumps(__get_codes_stats(repo_name))
229 229
230 230 try:
231 231 stats.repository = dbrepo
232 232 stats.stat_on_revision = last_cs.revision if last_cs else 0
233 233 sa.add(stats)
234 234 sa.commit()
235 235 except:
236 236 log.error(traceback.format_exc())
237 237 sa.rollback()
238 238 lock.release()
239 239 return False
240 240
241 241 #final release
242 242 lock.release()
243 243
244 244 #execute another task if celery is enabled
245 245 if len(repo.revisions) > 1 and CELERY_ON:
246 246 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
247 247 return True
248 248 except LockHeld:
249 249 log.info('LockHeld')
250 250 return 'Task with key %s already running' % lockkey
251 251
252 @task(ignore_result=True)
253 def send_password_link(user_email):
254 try:
255 log = reset_user_password.get_logger()
256 except:
257 log = logging.getLogger(__name__)
258
259 from rhodecode.lib import auth
260 from rhodecode.model.db import User
261
262 try:
263 sa = get_session()
264 user = sa.query(User).filter(User.email == user_email).scalar()
265
266 if user:
267 link = url('reset_password_confirmation', key=user.api_key,
268 qualified=True)
269 tmpl = """
270 Hello %s
271
272 We received a request to create a new password for your account.
273
274 You can generate it by clicking following URL:
275
276 %s
277
278 If you didn't request new password please ignore this email.
279 """
280 run_task(send_email, user_email,
281 "RhodeCode password reset link",
282 tmpl % (user.short_contact, link))
283 log.info('send new password mail to %s', user_email)
284
285 except:
286 log.error('Failed to update user password')
287 log.error(traceback.format_exc())
288 return False
289
290 return True
252 291
253 292 @task(ignore_result=True)
254 293 def reset_user_password(user_email):
255 294 try:
256 295 log = reset_user_password.get_logger()
257 296 except:
258 297 log = logging.getLogger(__name__)
259 298
260 299 from rhodecode.lib import auth
261 300 from rhodecode.model.db import User
262 301
263 302 try:
264 303 try:
265 304 sa = get_session()
266 305 user = sa.query(User).filter(User.email == user_email).scalar()
267 306 new_passwd = auth.PasswordGenerator().gen_password(8,
268 307 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
269 308 if user:
270 309 user.password = auth.get_crypt_password(new_passwd)
271 310 user.api_key = auth.generate_api_key(user.username)
272 311 sa.add(user)
273 312 sa.commit()
274 313 log.info('change password for %s', user_email)
275 314 if new_passwd is None:
276 315 raise Exception('unable to generate new password')
277 316
278 317 except:
279 318 log.error(traceback.format_exc())
280 319 sa.rollback()
281 320
282 321 run_task(send_email, user_email,
283 "Your new rhodecode password",
284 'Your new rhodecode password:%s' % (new_passwd))
322 "Your new RhodeCode password",
323 'Your new RhodeCode password:%s' % (new_passwd))
285 324 log.info('send new password mail to %s', user_email)
286 325
287 326 except:
288 327 log.error('Failed to update user password')
289 328 log.error(traceback.format_exc())
290 329
291 330 return True
292 331
293 332
294 333 @task(ignore_result=True)
295 334 def send_email(recipients, subject, body):
296 335 """
297 336 Sends an email with defined parameters from the .ini files.
298 337
299 338 :param recipients: list of recipients, it this is empty the defined email
300 339 address from field 'email_to' is used instead
301 340 :param subject: subject of the mail
302 341 :param body: body of the mail
303 342 """
304 343 try:
305 344 log = send_email.get_logger()
306 345 except:
307 346 log = logging.getLogger(__name__)
308 347
309 348 email_config = config
310 349
311 350 if not recipients:
312 351 recipients = [email_config.get('email_to')]
313 352
314 353 mail_from = email_config.get('app_email_from')
315 354 user = email_config.get('smtp_username')
316 355 passwd = email_config.get('smtp_password')
317 356 mail_server = email_config.get('smtp_server')
318 357 mail_port = email_config.get('smtp_port')
319 358 tls = str2bool(email_config.get('smtp_use_tls'))
320 359 ssl = str2bool(email_config.get('smtp_use_ssl'))
321 360 debug = str2bool(config.get('debug'))
322 361
323 362 try:
324 363 m = SmtpMailer(mail_from, user, passwd, mail_server,
325 364 mail_port, ssl, tls, debug=debug)
326 365 m.send(recipients, subject, body)
327 366 except:
328 367 log.error('Mail sending failed')
329 368 log.error(traceback.format_exc())
330 369 return False
331 370 return True
332 371
333 372
334 373 @task(ignore_result=True)
335 374 def create_repo_fork(form_data, cur_user):
336 375 from rhodecode.model.repo import RepoModel
337 376 from vcs import get_backend
338 377
339 378 try:
340 379 log = create_repo_fork.get_logger()
341 380 except:
342 381 log = logging.getLogger(__name__)
343 382
344 383 repo_model = RepoModel(get_session())
345 384 repo_model.create(form_data, cur_user, just_db=True, fork=True)
346 385 repo_name = form_data['repo_name']
347 386 repos_path = get_repos_path()
348 387 repo_path = os.path.join(repos_path, repo_name)
349 388 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
350 389 alias = form_data['repo_type']
351 390
352 391 log.info('creating repo fork %s as %s', repo_name, repo_path)
353 392 backend = get_backend(alias)
354 393 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
355 394
356 395
357 396 def __get_codes_stats(repo_name):
358 397 repos_path = get_repos_path()
359 398 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
360 399 tip = repo.get_changeset()
361 400 code_stats = {}
362 401
363 402 def aggregate(cs):
364 403 for f in cs[2]:
365 404 ext = lower(f.extension)
366 405 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
367 406 if ext in code_stats:
368 407 code_stats[ext] += 1
369 408 else:
370 409 code_stats[ext] = 1
371 410
372 411 map(aggregate, tip.walk('/'))
373 412
374 413 return code_stats or {}
@@ -1,143 +1,149 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.smtp_mailer
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Simple smtp mailer used in RhodeCode
7 7
8 8 :created_on: Sep 13, 2010
9 9 :copyright: (c) 2011 by marcink.
10 10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 11 """
12 12
13 13 import logging
14 14 import smtplib
15 15 import mimetypes
16 16 from socket import sslerror
17 17
18 18 from email.mime.multipart import MIMEMultipart
19 19 from email.mime.image import MIMEImage
20 20 from email.mime.audio import MIMEAudio
21 21 from email.mime.base import MIMEBase
22 22 from email.mime.text import MIMEText
23 23 from email.utils import formatdate
24 24 from email import encoders
25 25
26 26
27 27 class SmtpMailer(object):
28 28 """SMTP mailer class
29 29
30 30 mailer = SmtpMailer(mail_from, user, passwd, mail_server,
31 31 mail_port, ssl, tls)
32 32 mailer.send(recipients, subject, body, attachment_files)
33 33
34 34 :param recipients might be a list of string or single string
35 35 :param attachment_files is a dict of {filename:location}
36 36 it tries to guess the mimetype and attach the file
37 37
38 38 """
39 39
40 40 def __init__(self, mail_from, user, passwd, mail_server,
41 41 mail_port=None, ssl=False, tls=False, debug=False):
42 42
43 43 self.mail_from = mail_from
44 44 self.mail_server = mail_server
45 45 self.mail_port = mail_port
46 46 self.user = user
47 47 self.passwd = passwd
48 48 self.ssl = ssl
49 49 self.tls = tls
50 50 self.debug = debug
51 51
52 52 def send(self, recipients=[], subject='', body='', attachment_files=None):
53 53
54 54 if isinstance(recipients, basestring):
55 55 recipients = [recipients]
56 56 if self.ssl:
57 57 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
58 58 else:
59 59 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
60 60
61 61 if self.tls:
62 62 smtp_serv.ehlo()
63 63 smtp_serv.starttls()
64 64
65 65 if self.debug:
66 66 smtp_serv.set_debuglevel(1)
67 67
68 68 smtp_serv.ehlo()
69 69
70 70 #if server requires authorization you must provide login and password
71 71 #but only if we have them
72 72 if self.user and self.passwd:
73 73 smtp_serv.login(self.user, self.passwd)
74 74
75 75 date_ = formatdate(localtime=True)
76 76 msg = MIMEMultipart()
77 msg.set_type('multipart/alternative')
78 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
79
80 text_msg = MIMEText(body)
81 text_msg.set_type('text/plain')
82 text_msg.set_param('charset', 'UTF-8')
83
77 84 msg['From'] = self.mail_from
78 85 msg['To'] = ','.join(recipients)
79 86 msg['Date'] = date_
80 87 msg['Subject'] = subject
81 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
82 88
83 msg.attach(MIMEText(body))
89 msg.attach(text_msg)
84 90
85 91 if attachment_files:
86 92 self.__atach_files(msg, attachment_files)
87 93
88 94 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
89 95 logging.info('MAIL SEND TO: %s' % recipients)
90 96
91 97 try:
92 98 smtp_serv.quit()
93 99 except sslerror:
94 100 # sslerror is raised in tls connections on closing sometimes
95 101 pass
96 102
97 103 def __atach_files(self, msg, attachment_files):
98 104 if isinstance(attachment_files, dict):
99 105 for f_name, msg_file in attachment_files.items():
100 106 ctype, encoding = mimetypes.guess_type(f_name)
101 107 logging.info("guessing file %s type based on %s", ctype,
102 108 f_name)
103 109 if ctype is None or encoding is not None:
104 110 # No guess could be made, or the file is encoded
105 111 # (compressed), so use a generic bag-of-bits type.
106 112 ctype = 'application/octet-stream'
107 113 maintype, subtype = ctype.split('/', 1)
108 114 if maintype == 'text':
109 115 # Note: we should handle calculating the charset
110 116 file_part = MIMEText(self.get_content(msg_file),
111 117 _subtype=subtype)
112 118 elif maintype == 'image':
113 119 file_part = MIMEImage(self.get_content(msg_file),
114 120 _subtype=subtype)
115 121 elif maintype == 'audio':
116 122 file_part = MIMEAudio(self.get_content(msg_file),
117 123 _subtype=subtype)
118 124 else:
119 125 file_part = MIMEBase(maintype, subtype)
120 126 file_part.set_payload(self.get_content(msg_file))
121 127 # Encode the payload using Base64
122 128 encoders.encode_base64(msg)
123 129 # Set the filename parameter
124 130 file_part.add_header('Content-Disposition', 'attachment',
125 131 filename=f_name)
126 132 file_part.add_header('Content-Type', ctype, name=f_name)
127 133 msg.attach(file_part)
128 134 else:
129 135 raise Exception('Attachment files should be'
130 136 'a dict in format {"filename":"filepath"}')
131 137
132 138 def get_content(self, msg_file):
133 139 """Get content based on type, if content is a string do open first
134 140 else just read because it's a probably open file object
135 141
136 142 :param msg_file:
137 143 """
138 144 if isinstance(msg_file, str):
139 145 return open(msg_file, "rb").read()
140 146 else:
141 147 #just for safe seek to 0
142 148 msg_file.seek(0)
143 149 return msg_file.read()
@@ -1,795 +1,800 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) 2009-2011 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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 35 from sqlalchemy.orm.interfaces import MapperExtension
36 36
37 37 from beaker.cache import cache_region, region_invalidate
38 38
39 39 from vcs import get_backend
40 40 from vcs.utils.helpers import get_scm
41 41 from vcs.exceptions import RepositoryError, VCSError
42 42 from vcs.utils.lazy import LazyProperty
43 43 from vcs.nodes import FileNode
44 44
45 45 from rhodecode.lib import str2bool, json, safe_str
46 46 from rhodecode.model.meta import Base, Session
47 47 from rhodecode.model.caching_query import FromCache
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51 #==============================================================================
52 52 # BASE CLASSES
53 53 #==============================================================================
54 54
55 55 class ModelSerializer(json.JSONEncoder):
56 56 """
57 57 Simple Serializer for JSON,
58 58
59 59 usage::
60 60
61 61 to make object customized for serialization implement a __json__
62 62 method that will return a dict for serialization into json
63 63
64 64 example::
65 65
66 66 class Task(object):
67 67
68 68 def __init__(self, name, value):
69 69 self.name = name
70 70 self.value = value
71 71
72 72 def __json__(self):
73 73 return dict(name=self.name,
74 74 value=self.value)
75 75
76 76 """
77 77
78 78 def default(self, obj):
79 79
80 80 if hasattr(obj, '__json__'):
81 81 return obj.__json__()
82 82 else:
83 83 return json.JSONEncoder.default(self, obj)
84 84
85 85 class BaseModel(object):
86 86 """Base Model for all classess
87 87
88 88 """
89 89
90 90 @classmethod
91 91 def _get_keys(cls):
92 92 """return column names for this model """
93 93 return class_mapper(cls).c.keys()
94 94
95 95 def get_dict(self):
96 96 """return dict with keys and values corresponding
97 97 to this model data """
98 98
99 99 d = {}
100 100 for k in self._get_keys():
101 101 d[k] = getattr(self, k)
102 102 return d
103 103
104 104 def get_appstruct(self):
105 105 """return list with keys and values tupples corresponding
106 106 to this model data """
107 107
108 108 l = []
109 109 for k in self._get_keys():
110 110 l.append((k, getattr(self, k),))
111 111 return l
112 112
113 113 def populate_obj(self, populate_dict):
114 114 """populate model with data from given populate_dict"""
115 115
116 116 for k in self._get_keys():
117 117 if k in populate_dict:
118 118 setattr(self, k, populate_dict[k])
119 119
120 120 @classmethod
121 121 def query(cls):
122 122 return Session.query(cls)
123 123
124 124 @classmethod
125 125 def get(cls, id_):
126 126 return Session.query(cls).get(id_)
127 127
128 128
129 129 class RhodeCodeSettings(Base, BaseModel):
130 130 __tablename__ = 'rhodecode_settings'
131 131 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
132 132 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
133 133 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
134 134 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
135 135
136 136 def __init__(self, k='', v=''):
137 137 self.app_settings_name = k
138 138 self.app_settings_value = v
139 139
140 140 def __repr__(self):
141 141 return "<%s('%s:%s')>" % (self.__class__.__name__,
142 142 self.app_settings_name, self.app_settings_value)
143 143
144 144
145 145 @classmethod
146 146 def get_by_name(cls, ldap_key):
147 147 return Session.query(cls)\
148 148 .filter(cls.app_settings_name == ldap_key).scalar()
149 149
150 150 @classmethod
151 151 def get_app_settings(cls, cache=False):
152 152
153 153 ret = Session.query(cls)
154 154
155 155 if cache:
156 156 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
157 157
158 158 if not ret:
159 159 raise Exception('Could not get application settings !')
160 160 settings = {}
161 161 for each in ret:
162 162 settings['rhodecode_' + each.app_settings_name] = \
163 163 each.app_settings_value
164 164
165 165 return settings
166 166
167 167 @classmethod
168 168 def get_ldap_settings(cls, cache=False):
169 169 ret = Session.query(cls)\
170 170 .filter(cls.app_settings_name.startswith('ldap_'))\
171 171 .all()
172 172 fd = {}
173 173 for row in ret:
174 174 fd.update({row.app_settings_name:row.app_settings_value})
175 175
176 176 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
177 177
178 178 return fd
179 179
180 180
181 181 class RhodeCodeUi(Base, BaseModel):
182 182 __tablename__ = 'rhodecode_ui'
183 183 __table_args__ = {'extend_existing':True}
184 184 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
185 185 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
186 186 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
187 187 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
188 188 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
189 189
190 190
191 191 @classmethod
192 192 def get_by_key(cls, key):
193 193 return Session.query(cls).filter(cls.ui_key == key)
194 194
195 195
196 196 class User(Base, BaseModel):
197 197 __tablename__ = 'users'
198 198 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
199 199 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 200 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 201 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 202 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
203 203 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
204 204 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
205 205 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
206 206 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
207 207 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
208 208 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
209 209 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
210 210
211 211 user_log = relationship('UserLog', cascade='all')
212 212 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
213 213
214 214 repositories = relationship('Repository')
215 215 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
216 216 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
217 217
218 218 group_member = relationship('UsersGroupMember', cascade='all')
219 219
220 220 @property
221 221 def full_contact(self):
222 222 return '%s %s <%s>' % (self.name, self.lastname, self.email)
223 223
224 224 @property
225 225 def short_contact(self):
226 226 return '%s %s' % (self.name, self.lastname)
227 227
228 228 @property
229 229 def is_admin(self):
230 230 return self.admin
231 231
232 232 def __repr__(self):
233 233 try:
234 234 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
235 235 self.user_id, self.username)
236 236 except:
237 237 return self.__class__.__name__
238 238
239 239 @classmethod
240 240 def by_username(cls, username, case_insensitive=False):
241 241 if case_insensitive:
242 242 return Session.query(cls).filter(cls.username.like(username)).one()
243 243 else:
244 244 return Session.query(cls).filter(cls.username == username).one()
245 245
246 @classmethod
247 def get_by_api_key(cls, api_key):
248 return Session.query(cls).filter(cls.api_key == api_key).one()
249
250
246 251 def update_lastlogin(self):
247 252 """Update user lastlogin"""
248 253
249 254 self.last_login = datetime.datetime.now()
250 255 Session.add(self)
251 256 Session.commit()
252 257 log.debug('updated user %s lastlogin', self.username)
253 258
254 259
255 260 class UserLog(Base, BaseModel):
256 261 __tablename__ = 'user_logs'
257 262 __table_args__ = {'extend_existing':True}
258 263 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
259 264 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
260 265 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
261 266 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
262 267 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 268 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 269 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
265 270
266 271 @property
267 272 def action_as_day(self):
268 273 return date(*self.action_date.timetuple()[:3])
269 274
270 275 user = relationship('User')
271 276 repository = relationship('Repository')
272 277
273 278
274 279 class UsersGroup(Base, BaseModel):
275 280 __tablename__ = 'users_groups'
276 281 __table_args__ = {'extend_existing':True}
277 282
278 283 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 284 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
280 285 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
281 286
282 287 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
283 288
284 289
285 290 @classmethod
286 291 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
287 292 if case_insensitive:
288 293 gr = Session.query(cls)\
289 294 .filter(cls.users_group_name.ilike(group_name))
290 295 else:
291 296 gr = Session.query(UsersGroup)\
292 297 .filter(UsersGroup.users_group_name == group_name)
293 298 if cache:
294 299 gr = gr.options(FromCache("sql_cache_short",
295 300 "get_user_%s" % group_name))
296 301 return gr.scalar()
297 302
298 303 class UsersGroupMember(Base, BaseModel):
299 304 __tablename__ = 'users_groups_members'
300 305 __table_args__ = {'extend_existing':True}
301 306
302 307 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
303 308 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
304 309 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
305 310
306 311 user = relationship('User', lazy='joined')
307 312 users_group = relationship('UsersGroup')
308 313
309 314 def __init__(self, gr_id='', u_id=''):
310 315 self.users_group_id = gr_id
311 316 self.user_id = u_id
312 317
313 318 class Repository(Base, BaseModel):
314 319 __tablename__ = 'repositories'
315 320 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
316 321
317 322 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
318 323 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
319 324 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
320 325 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
321 326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
322 327 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
323 328 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
324 329 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
325 330 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 331 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
327 332
328 333 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
329 334 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
330 335
331 336
332 337 user = relationship('User')
333 338 fork = relationship('Repository', remote_side=repo_id)
334 339 group = relationship('Group')
335 340 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
336 341 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
337 342 stats = relationship('Statistics', cascade='all', uselist=False)
338 343
339 344 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
340 345
341 346 logs = relationship('UserLog', cascade='all')
342 347
343 348 def __repr__(self):
344 349 return "<%s('%s:%s')>" % (self.__class__.__name__,
345 350 self.repo_id, self.repo_name)
346 351
347 352 @classmethod
348 353 def by_repo_name(cls, repo_name):
349 354 q = Session.query(cls).filter(cls.repo_name == repo_name)
350 355
351 356 q = q.options(joinedload(Repository.fork))\
352 357 .options(joinedload(Repository.user))\
353 358 .options(joinedload(Repository.group))\
354 359
355 360 return q.one()
356 361
357 362 @classmethod
358 363 def get_repo_forks(cls, repo_id):
359 364 return Session.query(cls).filter(Repository.fork_id == repo_id)
360 365
361 366 @property
362 367 def just_name(self):
363 368 return self.repo_name.split(os.sep)[-1]
364 369
365 370 @property
366 371 def groups_with_parents(self):
367 372 groups = []
368 373 if self.group is None:
369 374 return groups
370 375
371 376 cur_gr = self.group
372 377 groups.insert(0, cur_gr)
373 378 while 1:
374 379 gr = getattr(cur_gr, 'parent_group', None)
375 380 cur_gr = cur_gr.parent_group
376 381 if gr is None:
377 382 break
378 383 groups.insert(0, gr)
379 384
380 385 return groups
381 386
382 387 @property
383 388 def groups_and_repo(self):
384 389 return self.groups_with_parents, self.just_name
385 390
386 391 @LazyProperty
387 392 def repo_path(self):
388 393 """
389 394 Returns base full path for that repository means where it actually
390 395 exists on a filesystem
391 396 """
392 397 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
393 398 q.options(FromCache("sql_cache_short", "repository_repo_path"))
394 399 return q.one().ui_value
395 400
396 401 @property
397 402 def repo_full_path(self):
398 403 p = [self.repo_path]
399 404 # we need to split the name by / since this is how we store the
400 405 # names in the database, but that eventually needs to be converted
401 406 # into a valid system path
402 407 p += self.repo_name.split('/')
403 408 return os.path.join(*p)
404 409
405 410 @property
406 411 def _ui(self):
407 412 """
408 413 Creates an db based ui object for this repository
409 414 """
410 415 from mercurial import ui
411 416 from mercurial import config
412 417 baseui = ui.ui()
413 418
414 419 #clean the baseui object
415 420 baseui._ocfg = config.config()
416 421 baseui._ucfg = config.config()
417 422 baseui._tcfg = config.config()
418 423
419 424
420 425 ret = Session.query(RhodeCodeUi)\
421 426 .options(FromCache("sql_cache_short",
422 427 "repository_repo_ui")).all()
423 428
424 429 hg_ui = ret
425 430 for ui_ in hg_ui:
426 431 if ui_.ui_active:
427 432 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
428 433 ui_.ui_key, ui_.ui_value)
429 434 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
430 435
431 436 return baseui
432 437
433 438 #==========================================================================
434 439 # SCM CACHE INSTANCE
435 440 #==========================================================================
436 441
437 442 @property
438 443 def invalidate(self):
439 444 """
440 445 Returns Invalidation object if this repo should be invalidated
441 446 None otherwise. `cache_active = False` means that this cache
442 447 state is not valid and needs to be invalidated
443 448 """
444 449 return Session.query(CacheInvalidation)\
445 450 .filter(CacheInvalidation.cache_key == self.repo_name)\
446 451 .filter(CacheInvalidation.cache_active == False)\
447 452 .scalar()
448 453
449 454 @property
450 455 def set_invalidate(self):
451 456 """
452 457 set a cache for invalidation for this instance
453 458 """
454 459 inv = Session.query(CacheInvalidation)\
455 460 .filter(CacheInvalidation.cache_key == self.repo_name)\
456 461 .scalar()
457 462
458 463 if inv is None:
459 464 inv = CacheInvalidation(self.repo_name)
460 465 inv.cache_active = True
461 466 Session.add(inv)
462 467 Session.commit()
463 468
464 469 @property
465 470 def scm_instance(self):
466 471 return self.__get_instance()
467 472
468 473 @property
469 474 def scm_instance_cached(self):
470 475 @cache_region('long_term')
471 476 def _c(repo_name):
472 477 return self.__get_instance()
473 478
474 479 inv = self.invalidate
475 480 if inv:
476 481 region_invalidate(_c, None, self.repo_name)
477 482 #update our cache
478 483 inv.cache_key.cache_active = True
479 484 Session.add(inv)
480 485 Session.commit()
481 486
482 487 # TODO: remove this trick when beaker 1.6 is released
483 488 # and have fixed this issue
484 489 rn = safe_str(self.repo_name)
485 490
486 491 return _c(rn)
487 492
488 493 def __get_instance(self):
489 494
490 495 repo_full_path = self.repo_full_path
491 496
492 497 try:
493 498 alias = get_scm(repo_full_path)[0]
494 499 log.debug('Creating instance of %s repository', alias)
495 500 backend = get_backend(alias)
496 501 except VCSError:
497 502 log.error(traceback.format_exc())
498 503 log.error('Perhaps this repository is in db and not in '
499 504 'filesystem run rescan repositories with '
500 505 '"destroy old data " option from admin panel')
501 506 return
502 507
503 508 if alias == 'hg':
504 509
505 510 repo = backend(safe_str(repo_full_path), create=False,
506 511 baseui=self._ui)
507 512 #skip hidden web repository
508 513 if repo._get_hidden():
509 514 return
510 515 else:
511 516 repo = backend(repo_full_path, create=False)
512 517
513 518 return repo
514 519
515 520
516 521 class Group(Base, BaseModel):
517 522 __tablename__ = 'groups'
518 523 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
519 524 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
520 525 __mapper_args__ = {'order_by':'group_name'}
521 526
522 527 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
523 528 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
524 529 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
525 530 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
526 531
527 532 parent_group = relationship('Group', remote_side=group_id)
528 533
529 534
530 535 def __init__(self, group_name='', parent_group=None):
531 536 self.group_name = group_name
532 537 self.parent_group = parent_group
533 538
534 539 def __repr__(self):
535 540 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
536 541 self.group_name)
537 542
538 543 @classmethod
539 544 def url_sep(cls):
540 545 return '/'
541 546
542 547 @property
543 548 def parents(self):
544 549 parents_recursion_limit = 5
545 550 groups = []
546 551 if self.parent_group is None:
547 552 return groups
548 553 cur_gr = self.parent_group
549 554 groups.insert(0, cur_gr)
550 555 cnt = 0
551 556 while 1:
552 557 cnt += 1
553 558 gr = getattr(cur_gr, 'parent_group', None)
554 559 cur_gr = cur_gr.parent_group
555 560 if gr is None:
556 561 break
557 562 if cnt == parents_recursion_limit:
558 563 # this will prevent accidental infinit loops
559 564 log.error('group nested more than %s' %
560 565 parents_recursion_limit)
561 566 break
562 567
563 568 groups.insert(0, gr)
564 569 return groups
565 570
566 571 @property
567 572 def children(self):
568 573 return Session.query(Group).filter(Group.parent_group == self)
569 574
570 575 @property
571 576 def full_path(self):
572 577 return Group.url_sep().join([g.group_name for g in self.parents] +
573 578 [self.group_name])
574 579
575 580 @property
576 581 def repositories(self):
577 582 return Session.query(Repository).filter(Repository.group == self)
578 583
579 584 @property
580 585 def repositories_recursive_count(self):
581 586 cnt = self.repositories.count()
582 587
583 588 def children_count(group):
584 589 cnt = 0
585 590 for child in group.children:
586 591 cnt += child.repositories.count()
587 592 cnt += children_count(child)
588 593 return cnt
589 594
590 595 return cnt + children_count(self)
591 596
592 597 class Permission(Base, BaseModel):
593 598 __tablename__ = 'permissions'
594 599 __table_args__ = {'extend_existing':True}
595 600 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
596 601 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 602 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 603
599 604 def __repr__(self):
600 605 return "<%s('%s:%s')>" % (self.__class__.__name__,
601 606 self.permission_id, self.permission_name)
602 607
603 608 @classmethod
604 609 def get_by_key(cls, key):
605 610 return Session.query(cls).filter(cls.permission_name == key).scalar()
606 611
607 612 class RepoToPerm(Base, BaseModel):
608 613 __tablename__ = 'repo_to_perm'
609 614 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
610 615 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
611 616 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
612 617 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
613 618 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
614 619
615 620 user = relationship('User')
616 621 permission = relationship('Permission')
617 622 repository = relationship('Repository')
618 623
619 624 class UserToPerm(Base, BaseModel):
620 625 __tablename__ = 'user_to_perm'
621 626 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
622 627 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
623 628 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
624 629 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
625 630
626 631 user = relationship('User')
627 632 permission = relationship('Permission')
628 633
629 634 @classmethod
630 635 def has_perm(cls, user_id, perm):
631 636 if not isinstance(perm, Permission):
632 637 raise Exception('perm needs to be an instance of Permission class')
633 638
634 639 return Session.query(cls).filter(cls.user_id == user_id)\
635 640 .filter(cls.permission == perm).scalar() is not None
636 641
637 642 @classmethod
638 643 def grant_perm(cls, user_id, perm):
639 644 if not isinstance(perm, Permission):
640 645 raise Exception('perm needs to be an instance of Permission class')
641 646
642 647 new = cls()
643 648 new.user_id = user_id
644 649 new.permission = perm
645 650 try:
646 651 Session.add(new)
647 652 Session.commit()
648 653 except:
649 654 Session.rollback()
650 655
651 656
652 657 @classmethod
653 658 def revoke_perm(cls, user_id, perm):
654 659 if not isinstance(perm, Permission):
655 660 raise Exception('perm needs to be an instance of Permission class')
656 661
657 662 try:
658 663 Session.query(cls).filter(cls.user_id == user_id)\
659 664 .filter(cls.permission == perm).delete()
660 665 Session.commit()
661 666 except:
662 667 Session.rollback()
663 668
664 669 class UsersGroupRepoToPerm(Base, BaseModel):
665 670 __tablename__ = 'users_group_repo_to_perm'
666 671 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
667 672 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
668 673 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
669 674 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
670 675 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
671 676
672 677 users_group = relationship('UsersGroup')
673 678 permission = relationship('Permission')
674 679 repository = relationship('Repository')
675 680
676 681
677 682 class UsersGroupToPerm(Base, BaseModel):
678 683 __tablename__ = 'users_group_to_perm'
679 684 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
680 685 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
681 686 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
682 687
683 688 users_group = relationship('UsersGroup')
684 689 permission = relationship('Permission')
685 690
686 691
687 692 @classmethod
688 693 def has_perm(cls, users_group_id, perm):
689 694 if not isinstance(perm, Permission):
690 695 raise Exception('perm needs to be an instance of Permission class')
691 696
692 697 return Session.query(cls).filter(cls.users_group_id ==
693 698 users_group_id)\
694 699 .filter(cls.permission == perm)\
695 700 .scalar() is not None
696 701
697 702 @classmethod
698 703 def grant_perm(cls, users_group_id, perm):
699 704 if not isinstance(perm, Permission):
700 705 raise Exception('perm needs to be an instance of Permission class')
701 706
702 707 new = cls()
703 708 new.users_group_id = users_group_id
704 709 new.permission = perm
705 710 try:
706 711 Session.add(new)
707 712 Session.commit()
708 713 except:
709 714 Session.rollback()
710 715
711 716
712 717 @classmethod
713 718 def revoke_perm(cls, users_group_id, perm):
714 719 if not isinstance(perm, Permission):
715 720 raise Exception('perm needs to be an instance of Permission class')
716 721
717 722 try:
718 723 Session.query(cls).filter(cls.users_group_id == users_group_id)\
719 724 .filter(cls.permission == perm).delete()
720 725 Session.commit()
721 726 except:
722 727 Session.rollback()
723 728
724 729
725 730 class GroupToPerm(Base, BaseModel):
726 731 __tablename__ = 'group_to_perm'
727 732 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
728 733
729 734 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
730 735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
731 736 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
732 737 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
733 738
734 739 user = relationship('User')
735 740 permission = relationship('Permission')
736 741 group = relationship('Group')
737 742
738 743 class Statistics(Base, BaseModel):
739 744 __tablename__ = 'statistics'
740 745 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
741 746 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
742 747 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
743 748 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
744 749 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
745 750 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
746 751 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
747 752
748 753 repository = relationship('Repository', single_parent=True)
749 754
750 755 class UserFollowing(Base, BaseModel):
751 756 __tablename__ = 'user_followings'
752 757 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
753 758 UniqueConstraint('user_id', 'follows_user_id')
754 759 , {'extend_existing':True})
755 760
756 761 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
757 762 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
758 763 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
759 764 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
760 765 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
761 766
762 767 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
763 768
764 769 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
765 770 follows_repository = relationship('Repository', order_by='Repository.repo_name')
766 771
767 772
768 773 @classmethod
769 774 def get_repo_followers(cls, repo_id):
770 775 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
771 776
772 777 class CacheInvalidation(Base, BaseModel):
773 778 __tablename__ = 'cache_invalidation'
774 779 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
775 780 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
776 781 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
777 782 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
778 783 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
779 784
780 785
781 786 def __init__(self, cache_key, cache_args=''):
782 787 self.cache_key = cache_key
783 788 self.cache_args = cache_args
784 789 self.cache_active = False
785 790
786 791 def __repr__(self):
787 792 return "<%s('%s:%s')>" % (self.__class__.__name__,
788 793 self.cache_id, self.cache_key)
789 794
790 795 class DbMigrateVersion(Base, BaseModel):
791 796 __tablename__ = 'db_migrate_version'
792 797 __table_args__ = {'extend_existing':True}
793 798 repository_id = Column('repository_id', String(250), primary_key=True)
794 799 repository_path = Column('repository_path', Text)
795 800 version = Column('version', Integer)
@@ -1,383 +1,387 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) 2009-2011 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
29 29 from pylons.i18n.translation import _
30 30
31 31 from rhodecode.model import BaseModel
32 32 from rhodecode.model.caching_query import FromCache
33 33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 34 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember
35 35 from rhodecode.lib.exceptions import DefaultUserException, \
36 36 UserOwnsReposException
37 37
38 38 from sqlalchemy.exc import DatabaseError
39 39 from rhodecode.lib import generate_api_key
40 40 from sqlalchemy.orm import joinedload
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44 PERM_WEIGHTS = {'repository.none': 0,
45 45 'repository.read': 1,
46 46 'repository.write': 3,
47 47 'repository.admin': 3}
48 48
49 49
50 50 class UserModel(BaseModel):
51 51
52 52 def get(self, user_id, cache=False):
53 53 user = self.sa.query(User)
54 54 if cache:
55 55 user = user.options(FromCache("sql_cache_short",
56 56 "get_user_%s" % user_id))
57 57 return user.get(user_id)
58 58
59 59 def get_by_username(self, username, cache=False, case_insensitive=False):
60 60
61 61 if case_insensitive:
62 62 user = self.sa.query(User).filter(User.username.ilike(username))
63 63 else:
64 64 user = self.sa.query(User)\
65 65 .filter(User.username == username)
66 66 if cache:
67 67 user = user.options(FromCache("sql_cache_short",
68 68 "get_user_%s" % username))
69 69 return user.scalar()
70 70
71 71 def get_by_api_key(self, api_key, cache=False):
72 72
73 73 user = self.sa.query(User)\
74 74 .filter(User.api_key == api_key)
75 75 if cache:
76 76 user = user.options(FromCache("sql_cache_short",
77 77 "get_user_%s" % api_key))
78 78 return user.scalar()
79 79
80 80 def create(self, form_data):
81 81 try:
82 82 new_user = User()
83 83 for k, v in form_data.items():
84 84 setattr(new_user, k, v)
85 85
86 86 new_user.api_key = generate_api_key(form_data['username'])
87 87 self.sa.add(new_user)
88 88 self.sa.commit()
89 89 except:
90 90 log.error(traceback.format_exc())
91 91 self.sa.rollback()
92 92 raise
93 93
94 94 def create_ldap(self, username, password, user_dn, attrs):
95 95 """
96 96 Checks if user is in database, if not creates this user marked
97 97 as ldap user
98 98 :param username:
99 99 :param password:
100 100 :param user_dn:
101 101 :param attrs:
102 102 """
103 103 from rhodecode.lib.auth import get_crypt_password
104 104 log.debug('Checking for such ldap account in RhodeCode database')
105 105 if self.get_by_username(username, case_insensitive=True) is None:
106 106 try:
107 107 new_user = User()
108 108 # add ldap account always lowercase
109 109 new_user.username = username.lower()
110 110 new_user.password = get_crypt_password(password)
111 111 new_user.api_key = generate_api_key(username)
112 112 new_user.email = attrs['email']
113 113 new_user.active = True
114 114 new_user.ldap_dn = user_dn
115 115 new_user.name = attrs['name']
116 116 new_user.lastname = attrs['lastname']
117 117
118 118 self.sa.add(new_user)
119 119 self.sa.commit()
120 120 return True
121 121 except (DatabaseError,):
122 122 log.error(traceback.format_exc())
123 123 self.sa.rollback()
124 124 raise
125 125 log.debug('this %s user exists skipping creation of ldap account',
126 126 username)
127 127 return False
128 128
129 129 def create_registration(self, form_data):
130 130 from rhodecode.lib.celerylib import tasks, run_task
131 131 try:
132 132 new_user = User()
133 133 for k, v in form_data.items():
134 134 if k != 'admin':
135 135 setattr(new_user, k, v)
136 136
137 137 self.sa.add(new_user)
138 138 self.sa.commit()
139 139 body = ('New user registration\n'
140 140 'username: %s\n'
141 141 'email: %s\n')
142 142 body = body % (form_data['username'], form_data['email'])
143 143
144 144 run_task(tasks.send_email, None,
145 145 _('[RhodeCode] New User registration'),
146 146 body)
147 147 except:
148 148 log.error(traceback.format_exc())
149 149 self.sa.rollback()
150 150 raise
151 151
152 152 def update(self, user_id, form_data):
153 153 try:
154 154 user = self.get(user_id, cache=False)
155 155 if user.username == 'default':
156 156 raise DefaultUserException(
157 157 _("You can't Edit this user since it's"
158 158 " crucial for entire application"))
159 159
160 160 for k, v in form_data.items():
161 161 if k == 'new_password' and v != '':
162 162 user.password = v
163 163 user.api_key = generate_api_key(user.username)
164 164 else:
165 165 setattr(user, k, v)
166 166
167 167 self.sa.add(user)
168 168 self.sa.commit()
169 169 except:
170 170 log.error(traceback.format_exc())
171 171 self.sa.rollback()
172 172 raise
173 173
174 174 def update_my_account(self, user_id, form_data):
175 175 try:
176 176 user = self.get(user_id, cache=False)
177 177 if user.username == 'default':
178 178 raise DefaultUserException(
179 179 _("You can't Edit this user since it's"
180 180 " crucial for entire application"))
181 181 for k, v in form_data.items():
182 182 if k == 'new_password' and v != '':
183 183 user.password = v
184 184 user.api_key = generate_api_key(user.username)
185 185 else:
186 186 if k not in ['admin', 'active']:
187 187 setattr(user, k, v)
188 188
189 189 self.sa.add(user)
190 190 self.sa.commit()
191 191 except:
192 192 log.error(traceback.format_exc())
193 193 self.sa.rollback()
194 194 raise
195 195
196 196 def delete(self, user_id):
197 197 try:
198 198 user = self.get(user_id, cache=False)
199 199 if user.username == 'default':
200 200 raise DefaultUserException(
201 201 _("You can't remove this user since it's"
202 202 " crucial for entire application"))
203 203 if user.repositories:
204 204 raise UserOwnsReposException(_('This user still owns %s '
205 205 'repositories and cannot be '
206 206 'removed. Switch owners or '
207 207 'remove those repositories') \
208 208 % user.repositories)
209 209 self.sa.delete(user)
210 210 self.sa.commit()
211 211 except:
212 212 log.error(traceback.format_exc())
213 213 self.sa.rollback()
214 214 raise
215 215
216 def reset_password_link(self, data):
217 from rhodecode.lib.celerylib import tasks, run_task
218 run_task(tasks.send_password_link, data['email'])
219
216 220 def reset_password(self, data):
217 221 from rhodecode.lib.celerylib import tasks, run_task
218 222 run_task(tasks.reset_user_password, data['email'])
219 223
220 224 def fill_data(self, auth_user, user_id=None, api_key=None):
221 225 """
222 226 Fetches auth_user by user_id,or api_key if present.
223 227 Fills auth_user attributes with those taken from database.
224 228 Additionally set's is_authenitated if lookup fails
225 229 present in database
226 230
227 231 :param auth_user: instance of user to set attributes
228 232 :param user_id: user id to fetch by
229 233 :param api_key: api key to fetch by
230 234 """
231 235 if user_id is None and api_key is None:
232 236 raise Exception('You need to pass user_id or api_key')
233 237
234 238 try:
235 239 if api_key:
236 240 dbuser = self.get_by_api_key(api_key)
237 241 else:
238 242 dbuser = self.get(user_id)
239 243
240 244 if dbuser is not None:
241 245 log.debug('filling %s data', dbuser)
242 246 for k, v in dbuser.get_dict().items():
243 247 setattr(auth_user, k, v)
244 248
245 249 except:
246 250 log.error(traceback.format_exc())
247 251 auth_user.is_authenticated = False
248 252
249 253 return auth_user
250 254
251 255 def fill_perms(self, user):
252 256 """
253 257 Fills user permission attribute with permissions taken from database
254 258 works for permissions given for repositories, and for permissions that
255 259 are granted to groups
256 260
257 261 :param user: user instance to fill his perms
258 262 """
259 263
260 264 user.permissions['repositories'] = {}
261 265 user.permissions['global'] = set()
262 266
263 267 #======================================================================
264 268 # fetch default permissions
265 269 #======================================================================
266 270 default_user = self.get_by_username('default', cache=True)
267 271
268 272 default_perms = self.sa.query(RepoToPerm, Repository, Permission)\
269 273 .join((Repository, RepoToPerm.repository_id ==
270 274 Repository.repo_id))\
271 275 .join((Permission, RepoToPerm.permission_id ==
272 276 Permission.permission_id))\
273 277 .filter(RepoToPerm.user == default_user).all()
274 278
275 279 if user.is_admin:
276 280 #==================================================================
277 281 # #admin have all default rights set to admin
278 282 #==================================================================
279 283 user.permissions['global'].add('hg.admin')
280 284
281 285 for perm in default_perms:
282 286 p = 'repository.admin'
283 287 user.permissions['repositories'][perm.RepoToPerm.
284 288 repository.repo_name] = p
285 289
286 290 else:
287 291 #==================================================================
288 292 # set default permissions
289 293 #==================================================================
290 294 uid = user.user_id
291 295
292 296 #default global
293 297 default_global_perms = self.sa.query(UserToPerm)\
294 298 .filter(UserToPerm.user == default_user)
295 299
296 300 for perm in default_global_perms:
297 301 user.permissions['global'].add(perm.permission.permission_name)
298 302
299 303 #default for repositories
300 304 for perm in default_perms:
301 305 if perm.Repository.private and not (perm.Repository.user_id ==
302 306 uid):
303 307 #diself.sable defaults for private repos,
304 308 p = 'repository.none'
305 309 elif perm.Repository.user_id == uid:
306 310 #set admin if owner
307 311 p = 'repository.admin'
308 312 else:
309 313 p = perm.Permission.permission_name
310 314
311 315 user.permissions['repositories'][perm.RepoToPerm.
312 316 repository.repo_name] = p
313 317
314 318 #==================================================================
315 319 # overwrite default with user permissions if any
316 320 #==================================================================
317 321
318 322 #user global
319 323 user_perms = self.sa.query(UserToPerm)\
320 324 .options(joinedload(UserToPerm.permission))\
321 325 .filter(UserToPerm.user_id == uid).all()
322 326
323 327 for perm in user_perms:
324 328 user.permissions['global'].add(perm.permission.
325 329 permission_name)
326 330
327 331 #user repositories
328 332 user_repo_perms = self.sa.query(RepoToPerm, Permission,
329 333 Repository)\
330 334 .join((Repository, RepoToPerm.repository_id ==
331 335 Repository.repo_id))\
332 336 .join((Permission, RepoToPerm.permission_id ==
333 337 Permission.permission_id))\
334 338 .filter(RepoToPerm.user_id == uid).all()
335 339
336 340 for perm in user_repo_perms:
337 341 # set admin if owner
338 342 if perm.Repository.user_id == uid:
339 343 p = 'repository.admin'
340 344 else:
341 345 p = perm.Permission.permission_name
342 346 user.permissions['repositories'][perm.RepoToPerm.
343 347 repository.repo_name] = p
344 348
345 349 #==================================================================
346 350 # check if user is part of groups for this repository and fill in
347 351 # (or replace with higher) permissions
348 352 #==================================================================
349 353
350 354 #users group global
351 355 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
352 356 .options(joinedload(UsersGroupToPerm.permission))\
353 357 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
354 358 UsersGroupMember.users_group_id))\
355 359 .filter(UsersGroupMember.user_id == uid).all()
356 360
357 361 for perm in user_perms_from_users_groups:
358 362 user.permissions['global'].add(perm.permission.permission_name)
359 363
360 364 #users group repositories
361 365 user_repo_perms_from_users_groups = self.sa.query(
362 366 UsersGroupRepoToPerm,
363 367 Permission, Repository,)\
364 368 .join((Repository, UsersGroupRepoToPerm.repository_id ==
365 369 Repository.repo_id))\
366 370 .join((Permission, UsersGroupRepoToPerm.permission_id ==
367 371 Permission.permission_id))\
368 372 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id ==
369 373 UsersGroupMember.users_group_id))\
370 374 .filter(UsersGroupMember.user_id == uid).all()
371 375
372 376 for perm in user_repo_perms_from_users_groups:
373 377 p = perm.Permission.permission_name
374 378 cur_perm = user.permissions['repositories'][perm.
375 379 UsersGroupRepoToPerm.
376 380 repository.repo_name]
377 381 #overwrite permission only if it's greater than permission
378 382 # given from other sources
379 383 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
380 384 user.permissions['repositories'][perm.UsersGroupRepoToPerm.
381 385 repository.repo_name] = p
382 386
383 387 return user
@@ -1,2666 +1,2674 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 border:0;
3 3 outline:0;
4 4 font-size:100%;
5 5 vertical-align:baseline;
6 6 background:transparent;
7 7 margin:0;
8 8 padding:0;
9 9 }
10 10
11 11 body {
12 12 line-height:1;
13 13 height:100%;
14 14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
16 16 font-size:12px;
17 17 color:#000;
18 18 margin:0;
19 19 padding:0;
20 20 }
21 21
22 22 ol,ul {
23 23 list-style:none;
24 24 }
25 25
26 26 blockquote,q {
27 27 quotes:none;
28 28 }
29 29
30 30 blockquote:before,blockquote:after,q:before,q:after {
31 31 content:none;
32 32 }
33 33
34 34 :focus {
35 35 outline:0;
36 36 }
37 37
38 38 del {
39 39 text-decoration:line-through;
40 40 }
41 41
42 42 table {
43 43 border-collapse:collapse;
44 44 border-spacing:0;
45 45 }
46 46
47 47 html {
48 48 height:100%;
49 49 }
50 50
51 51 a {
52 52 color:#003367;
53 53 text-decoration:none;
54 54 cursor:pointer;
55 55 font-weight:700;
56 56 }
57 57
58 58 a:hover {
59 59 color:#316293;
60 60 text-decoration:underline;
61 61 }
62 62
63 63 h1,h2,h3,h4,h5,h6 {
64 64 color:#292929;
65 65 font-weight:700;
66 66 }
67 67
68 68 h1 {
69 69 font-size:22px;
70 70 }
71 71
72 72 h2 {
73 73 font-size:20px;
74 74 }
75 75
76 76 h3 {
77 77 font-size:18px;
78 78 }
79 79
80 80 h4 {
81 81 font-size:16px;
82 82 }
83 83
84 84 h5 {
85 85 font-size:14px;
86 86 }
87 87
88 88 h6 {
89 89 font-size:11px;
90 90 }
91 91
92 92 ul.circle {
93 93 list-style-type:circle;
94 94 }
95 95
96 96 ul.disc {
97 97 list-style-type:disc;
98 98 }
99 99
100 100 ul.square {
101 101 list-style-type:square;
102 102 }
103 103
104 104 ol.lower-roman {
105 105 list-style-type:lower-roman;
106 106 }
107 107
108 108 ol.upper-roman {
109 109 list-style-type:upper-roman;
110 110 }
111 111
112 112 ol.lower-alpha {
113 113 list-style-type:lower-alpha;
114 114 }
115 115
116 116 ol.upper-alpha {
117 117 list-style-type:upper-alpha;
118 118 }
119 119
120 120 ol.decimal {
121 121 list-style-type:decimal;
122 122 }
123 123
124 124 div.color {
125 125 clear:both;
126 126 overflow:hidden;
127 127 position:absolute;
128 128 background:#FFF;
129 129 margin:7px 0 0 60px;
130 130 padding:1px 1px 1px 0;
131 131 }
132 132
133 133 div.color a {
134 134 width:15px;
135 135 height:15px;
136 136 display:block;
137 137 float:left;
138 138 margin:0 0 0 1px;
139 139 padding:0;
140 140 }
141 141
142 142 div.options {
143 143 clear:both;
144 144 overflow:hidden;
145 145 position:absolute;
146 146 background:#FFF;
147 147 margin:7px 0 0 162px;
148 148 padding:0;
149 149 }
150 150
151 151 div.options a {
152 152 height:1%;
153 153 display:block;
154 154 text-decoration:none;
155 155 margin:0;
156 156 padding:3px 8px;
157 157 }
158 158
159 159 .top-left-rounded-corner {
160 160 -webkit-border-top-left-radius: 8px;
161 161 -khtml-border-radius-topleft: 8px;
162 162 -moz-border-radius-topleft: 8px;
163 163 border-top-left-radius: 8px;
164 164 }
165 165
166 166 .top-right-rounded-corner {
167 167 -webkit-border-top-right-radius: 8px;
168 168 -khtml-border-radius-topright: 8px;
169 169 -moz-border-radius-topright: 8px;
170 170 border-top-right-radius: 8px;
171 171 }
172 172
173 173 .bottom-left-rounded-corner {
174 174 -webkit-border-bottom-left-radius: 8px;
175 175 -khtml-border-radius-bottomleft: 8px;
176 176 -moz-border-radius-bottomleft: 8px;
177 177 border-bottom-left-radius: 8px;
178 178 }
179 179
180 180 .bottom-right-rounded-corner {
181 181 -webkit-border-bottom-right-radius: 8px;
182 182 -khtml-border-radius-bottomright: 8px;
183 183 -moz-border-radius-bottomright: 8px;
184 184 border-bottom-right-radius: 8px;
185 185 }
186 186
187 187
188 188 #header {
189 189 margin:0;
190 190 padding:0 10px;
191 191 }
192 192
193 193
194 194 #header ul#logged-user{
195 195 margin-bottom:5px !important;
196 196 -webkit-border-radius: 0px 0px 8px 8px;
197 197 -khtml-border-radius: 0px 0px 8px 8px;
198 198 -moz-border-radius: 0px 0px 8px 8px;
199 199 border-radius: 0px 0px 8px 8px;
200 200 height:37px;
201 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367
201 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
202 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
202 203 }
203 204
204 205 #header ul#logged-user li {
205 206 list-style:none;
206 207 float:left;
207 208 margin:8px 0 0;
208 209 padding:4px 12px;
209 210 border-left: 1px solid #316293;
210 211 }
211 212
212 213 #header ul#logged-user li.first {
213 214 border-left:none;
214 215 margin:4px;
215 216 }
216 217
217 218 #header ul#logged-user li.first div.gravatar {
218 219 margin-top:-2px;
219 220 }
220 221
221 222 #header ul#logged-user li.first div.account {
222 223 padding-top:4px;
223 224 float:left;
224 225 }
225 226
226 227 #header ul#logged-user li.last {
227 228 border-right:none;
228 229 }
229 230
230 231 #header ul#logged-user li a {
231 232 color:#fff;
232 233 font-weight:700;
233 234 text-decoration:none;
234 235 }
235 236
236 237 #header ul#logged-user li a:hover {
237 238 text-decoration:underline;
238 239 }
239 240
240 241 #header ul#logged-user li.highlight a {
241 242 color:#fff;
242 243 }
243 244
244 245 #header ul#logged-user li.highlight a:hover {
245 246 color:#FFF;
246 247 }
247 248
248 249 #header #header-inner {
249 250 height:40px;
250 251 clear:both;
251 252 position:relative;
252 253 background:#003367 url("../images/header_inner.png") repeat-x;
253 254 border-bottom:2px solid #fff;
254 255 margin:0;
255 256 padding:0;
256 257 }
257 258
258 259 #header #header-inner #home a {
259 260 height:40px;
260 261 width:46px;
261 262 display:block;
262 263 background:url("../images/button_home.png");
263 264 background-position:0 0;
264 265 margin:0;
265 266 padding:0;
266 267 }
267 268
268 269 #header #header-inner #home a:hover {
269 270 background-position:0 -40px;
270 271 }
271 272
272 273 #header #header-inner #logo h1 {
273 274 color:#FFF;
274 275 font-size:18px;
275 276 margin:10px 0 0 13px;
276 277 padding:0;
277 278 }
278 279
279 280 #header #header-inner #logo a {
280 281 color:#fff;
281 282 text-decoration:none;
282 283 }
283 284
284 285 #header #header-inner #logo a:hover {
285 286 color:#bfe3ff;
286 287 }
287 288
288 289 #header #header-inner #quick,#header #header-inner #quick ul {
289 290 position:relative;
290 291 float:right;
291 292 list-style-type:none;
292 293 list-style-position:outside;
293 294 margin:10px 5px 0 0;
294 295 padding:0;
295 296 }
296 297
297 298 #header #header-inner #quick li {
298 299 position:relative;
299 300 float:left;
300 301 margin:0 5px 0 0;
301 302 padding:0;
302 303 }
303 304
304 305 #header #header-inner #quick li a {
305 306 top:0;
306 307 left:0;
307 308 height:1%;
308 309 display:block;
309 310 clear:both;
310 311 overflow:hidden;
311 312 color:#FFF;
312 313 font-weight:700;
313 314 text-decoration:none;
314 315 background:#369 url("../images/quick_l.png") no-repeat top left;
315 316 padding:0;
316 317 }
317 318
318 319 #header #header-inner #quick li span.short {
319 320 padding:9px 6px 8px 6px;
320 321 }
321 322
322 323 #header #header-inner #quick li span {
323 324 top:0;
324 325 right:0;
325 326 height:1%;
326 327 display:block;
327 328 float:left;
328 329 background:url("../images/quick_r.png") no-repeat top right;
329 330 border-left:1px solid #3f6f9f;
330 331 margin:0;
331 332 padding:10px 12px 8px 10px;
332 333 }
333 334
334 335 #header #header-inner #quick li span.normal {
335 336 border:none;
336 337 padding:10px 12px 8px;
337 338 }
338 339
339 340 #header #header-inner #quick li span.icon {
340 341 top:0;
341 342 left:0;
342 343 border-left:none;
343 344 background:url("../images/quick_l.png") no-repeat top left;
344 345 border-right:1px solid #2e5c89;
345 346 padding:8px 8px 4px;
346 347 }
347 348
348 349 #header #header-inner #quick li span.icon_short {
349 350 top:0;
350 351 left:0;
351 352 border-left:none;
352 353 background:url("../images/quick_l.png") no-repeat top left;
353 354 border-right:1px solid #2e5c89;
354 355 padding:9px 4px 4px;
355 356 }
356 357
357 358 #header #header-inner #quick li a:hover {
358 359 background:#4e4e4e url("../images/quick_l_selected.png") no-repeat top left;
359 360 }
360 361
361 362 #header #header-inner #quick li a:hover span {
362 363 border-left:1px solid #545454;
363 364 background:url("../images/quick_r_selected.png") no-repeat top right;
364 365 }
365 366
366 367 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
367 368 border-left:none;
368 369 border-right:1px solid #464646;
369 370 background:url("../images/quick_l_selected.png") no-repeat top left;
370 371 }
371 372
372 373
373 374 #header #header-inner #quick ul {
374 375 top:29px;
375 376 right:0;
376 377 min-width:200px;
377 378 display:none;
378 379 position:absolute;
379 380 background:#FFF;
380 381 border:1px solid #666;
381 382 border-top:1px solid #003367;
382 383 z-index:100;
383 384 margin:0;
384 385 padding:0;
385 386 }
386 387
387 388 #header #header-inner #quick ul.repo_switcher {
388 389 max-height:275px;
389 390 overflow-x:hidden;
390 391 overflow-y:auto;
391 392 }
392 393 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
393 394 float:none;
394 395 margin:0;
395 396 border-bottom:2px solid #003367;
396 397 }
397 398
398 399
399 400 #header #header-inner #quick .repo_switcher_type{
400 401 position:absolute;
401 402 left:0;
402 403 top:9px;
403 404
404 405 }
405 406 #header #header-inner #quick li ul li {
406 407 border-bottom:1px solid #ddd;
407 408 }
408 409
409 410 #header #header-inner #quick li ul li a {
410 411 width:182px;
411 412 height:auto;
412 413 display:block;
413 414 float:left;
414 415 background:#FFF;
415 416 color:#003367;
416 417 font-weight:400;
417 418 margin:0;
418 419 padding:7px 9px;
419 420 }
420 421
421 422 #header #header-inner #quick li ul li a:hover {
422 423 color:#000;
423 424 background:#FFF;
424 425 }
425 426
426 427 #header #header-inner #quick ul ul {
427 428 top:auto;
428 429 }
429 430
430 431 #header #header-inner #quick li ul ul {
431 432 right:200px;
432 433 max-height:275px;
433 434 overflow:auto;
434 435 overflow-x:hidden;
435 436 white-space:normal;
436 437 }
437 438
438 439 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
439 440 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
440 441 width:167px;
441 442 margin:0;
442 443 padding:12px 9px 7px 24px;
443 444 }
444 445
445 446 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
446 447 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
447 448 min-width:167px;
448 449 margin:0;
449 450 padding:12px 9px 7px 24px;
450 451 }
451 452
452 453 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
453 454 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
454 455 min-width:167px;
455 456 margin:0;
456 457 padding:12px 9px 7px 24px;
457 458 }
458 459
459 460 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
460 461 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
461 462 min-width:167px;
462 463 margin:0 0 0 14px;
463 464 padding:12px 9px 7px 24px;
464 465 }
465 466
466 467 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
467 468 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
468 469 min-width:167px;
469 470 margin:0 0 0 14px;
470 471 padding:12px 9px 7px 24px;
471 472 }
472 473
473 474 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
474 475 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
475 476 width:167px;
476 477 margin:0;
477 478 padding:12px 9px 7px 24px;
478 479 }
479 480
480 481 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
481 482 background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
482 483 width:167px;
483 484 margin:0;
484 485 padding:12px 9px 7px 24px;
485 486 }
486 487
487 488 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
488 489 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
489 490 width:167px;
490 491 margin:0;
491 492 padding:12px 9px 7px 24px;
492 493 }
493 494
494 495 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
495 496 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
496 497 width:167px;
497 498 margin:0;
498 499 padding:12px 9px 7px 24px;
499 500 }
500 501
501 502 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
502 503 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
503 504 width:167px;
504 505 margin:0;
505 506 padding:12px 9px 7px 24px;
506 507 }
507 508
508 509 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
509 510 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
510 511 width:167px;
511 512 margin:0;
512 513 padding:12px 9px 7px 24px;
513 514 }
514 515
515 516 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
516 517 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
517 518 width:167px;
518 519 margin:0;
519 520 padding:12px 9px 7px 24px;
520 521 }
521 522
522 523 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
523 524 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
524 525 width:167px;
525 526 margin:0;
526 527 padding:12px 9px 7px 24px;
527 528 }
528 529
529 530 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
530 531 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
531 532 width:167px;
532 533 margin:0;
533 534 padding:12px 9px 7px 24px;
534 535 }
535 536
536 537 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
537 538 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
538 539 width:167px;
539 540 margin:0;
540 541 padding:12px 9px 7px 24px;
541 542 }
542 543
543 544 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
544 545 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
545 546 width:167px;
546 547 margin:0;
547 548 padding:12px 9px 7px 24px;
548 549 }
549 550
550 551 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
551 552 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
552 553 width:167px;
553 554 margin:0;
554 555 padding:12px 9px 7px 24px;
555 556 }
556 557
557 558 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
558 559 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
559 560 width:167px;
560 561 margin:0;
561 562 padding:12px 9px 7px 24px;
562 563 }
563 564
564 565 #content #left {
565 566 left:0;
566 567 width:280px;
567 568 position:absolute;
568 569 }
569 570
570 571 #content #right {
571 572 margin:0 60px 10px 290px;
572 573 }
573 574
574 575 #content div.box {
575 576 clear:both;
576 577 overflow:hidden;
577 578 background:#fff;
578 579 margin:0 0 10px;
579 580 padding:0 0 10px;
580 581 }
581 582
582 583 #content div.box-left {
583 584 width:49%;
584 585 clear:none;
585 586 float:left;
586 587 margin:0 0 10px;
587 588 }
588 589
589 590 #content div.box-right {
590 591 width:49%;
591 592 clear:none;
592 593 float:right;
593 594 margin:0 0 10px;
594 595 }
595 596
596 597 #content div.box div.title {
597 598 clear:both;
598 599 overflow:hidden;
599 600 background:#369 url("../images/header_inner.png") repeat-x;
600 601 margin:0 0 20px;
601 602 padding:0;
602 603 }
603 604
604 605 #content div.box div.title h5 {
605 606 float:left;
606 607 border:none;
607 608 color:#fff;
608 609 text-transform:uppercase;
609 610 margin:0;
610 611 padding:11px 0 11px 10px;
611 612 }
612 613
613 614 #content div.box div.title ul.links li {
614 615 list-style:none;
615 616 float:left;
616 617 margin:0;
617 618 padding:0;
618 619 }
619 620
620 621 #content div.box div.title ul.links li a {
621 622 border-left: 1px solid #316293;
622 623 color: #FFFFFF;
623 624 display: block;
624 625 float: left;
625 626 font-size: 13px;
626 627 font-weight: 700;
627 628 height: 1%;
628 629 margin: 0;
629 630 padding: 11px 22px 12px;
630 631 text-decoration: none;
631 632 }
632 633
633 634 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
634 635 clear:both;
635 636 overflow:hidden;
636 637 border-bottom:1px solid #DDD;
637 638 margin:10px 20px;
638 639 padding:0 0 15px;
639 640 }
640 641
641 642 #content div.box p {
642 643 color:#5f5f5f;
643 644 font-size:12px;
644 645 line-height:150%;
645 646 margin:0 24px 10px;
646 647 padding:0;
647 648 }
648 649
649 650 #content div.box blockquote {
650 651 border-left:4px solid #DDD;
651 652 color:#5f5f5f;
652 653 font-size:11px;
653 654 line-height:150%;
654 655 margin:0 34px;
655 656 padding:0 0 0 14px;
656 657 }
657 658
658 659 #content div.box blockquote p {
659 660 margin:10px 0;
660 661 padding:0;
661 662 }
662 663
663 664 #content div.box dl {
664 665 margin:10px 24px;
665 666 }
666 667
667 668 #content div.box dt {
668 669 font-size:12px;
669 670 margin:0;
670 671 }
671 672
672 673 #content div.box dd {
673 674 font-size:12px;
674 675 margin:0;
675 676 padding:8px 0 8px 15px;
676 677 }
677 678
678 679 #content div.box li {
679 680 font-size:12px;
680 681 padding:4px 0;
681 682 }
682 683
683 684 #content div.box ul.disc,#content div.box ul.circle {
684 685 margin:10px 24px 10px 38px;
685 686 }
686 687
687 688 #content div.box ul.square {
688 689 margin:10px 24px 10px 40px;
689 690 }
690 691
691 692 #content div.box img.left {
692 693 border:none;
693 694 float:left;
694 695 margin:10px 10px 10px 0;
695 696 }
696 697
697 698 #content div.box img.right {
698 699 border:none;
699 700 float:right;
700 701 margin:10px 0 10px 10px;
701 702 }
702 703
703 704 #content div.box div.messages {
704 705 clear:both;
705 706 overflow:hidden;
706 707 margin:0 20px;
707 708 padding:0;
708 709 }
709 710
710 711 #content div.box div.message {
711 712 clear:both;
712 713 overflow:hidden;
713 714 margin:0;
714 715 padding:10px 0;
715 716 }
716 717
717 718 #content div.box div.message a {
718 719 font-weight:400 !important;
719 720 }
720 721
721 722 #content div.box div.message div.image {
722 723 float:left;
723 724 margin:9px 0 0 5px;
724 725 padding:6px;
725 726 }
726 727
727 728 #content div.box div.message div.image img {
728 729 vertical-align:middle;
729 730 margin:0;
730 731 }
731 732
732 733 #content div.box div.message div.text {
733 734 float:left;
734 735 margin:0;
735 736 padding:9px 6px;
736 737 }
737 738
738 739 #content div.box div.message div.dismiss a {
739 740 height:16px;
740 741 width:16px;
741 742 display:block;
742 743 background:url("../images/icons/cross.png") no-repeat;
743 744 margin:15px 14px 0 0;
744 745 padding:0;
745 746 }
746 747
747 748 #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 {
748 749 border:none;
749 750 margin:0;
750 751 padding:0;
751 752 }
752 753
753 754 #content div.box div.message div.text span {
754 755 height:1%;
755 756 display:block;
756 757 margin:0;
757 758 padding:5px 0 0;
758 759 }
759 760
760 761 #content div.box div.message-error {
761 762 height:1%;
762 763 clear:both;
763 764 overflow:hidden;
764 765 background:#FBE3E4;
765 766 border:1px solid #FBC2C4;
766 767 color:#860006;
767 768 }
768 769
769 770 #content div.box div.message-error h6 {
770 771 color:#860006;
771 772 }
772 773
773 774 #content div.box div.message-warning {
774 775 height:1%;
775 776 clear:both;
776 777 overflow:hidden;
777 778 background:#FFF6BF;
778 779 border:1px solid #FFD324;
779 780 color:#5f5200;
780 781 }
781 782
782 783 #content div.box div.message-warning h6 {
783 784 color:#5f5200;
784 785 }
785 786
786 787 #content div.box div.message-notice {
787 788 height:1%;
788 789 clear:both;
789 790 overflow:hidden;
790 791 background:#8FBDE0;
791 792 border:1px solid #6BACDE;
792 793 color:#003863;
793 794 }
794 795
795 796 #content div.box div.message-notice h6 {
796 797 color:#003863;
797 798 }
798 799
799 800 #content div.box div.message-success {
800 801 height:1%;
801 802 clear:both;
802 803 overflow:hidden;
803 804 background:#E6EFC2;
804 805 border:1px solid #C6D880;
805 806 color:#4e6100;
806 807 }
807 808
808 809 #content div.box div.message-success h6 {
809 810 color:#4e6100;
810 811 }
811 812
812 813 #content div.box div.form div.fields div.field {
813 814 height:1%;
814 815 border-bottom:1px solid #DDD;
815 816 clear:both;
816 817 margin:0;
817 818 padding:10px 0;
818 819 }
819 820
820 821 #content div.box div.form div.fields div.field-first {
821 822 padding:0 0 10px;
822 823 }
823 824
824 825 #content div.box div.form div.fields div.field-noborder {
825 826 border-bottom:0 !important;
826 827 }
827 828
828 829 #content div.box div.form div.fields div.field span.error-message {
829 830 height:1%;
830 831 display:inline-block;
831 832 color:red;
832 833 margin:8px 0 0 4px;
833 834 padding:0;
834 835 }
835 836
836 837 #content div.box div.form div.fields div.field span.success {
837 838 height:1%;
838 839 display:block;
839 840 color:#316309;
840 841 margin:8px 0 0;
841 842 padding:0;
842 843 }
843 844
844 845 #content div.box div.form div.fields div.field div.label {
845 846 left:70px;
846 847 width:auto;
847 848 position:absolute;
848 849 margin:0;
849 850 padding:8px 0 0 5px;
850 851 }
851 852
852 853 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
853 854 clear:both;
854 855 overflow:hidden;
855 856 left:0;
856 857 width:auto;
857 858 position:relative;
858 859 margin:0;
859 860 padding:0 0 8px;
860 861 }
861 862
862 863 #content div.box div.form div.fields div.field div.label-select {
863 864 padding:5px 0 0 5px;
864 865 }
865 866
866 867 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
867 868 padding:0 0 8px;
868 869 }
869 870
870 871 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
871 872 padding:0 0 8px !important;
872 873 }
873 874
874 875 #content div.box div.form div.fields div.field div.label label, div.label label{
875 876 color:#393939;
876 877 font-weight:700;
877 878 }
878 879
879 880 #content div.box div.form div.fields div.field div.input {
880 881 margin:0 0 0 200px;
881 882 }
882 883 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
883 884 margin:0 0 0 0px;
884 885 }
885 886
886 887 #content div.box div.form div.fields div.field div.input input {
887 888 background:#FFF;
888 889 border-top:1px solid #b3b3b3;
889 890 border-left:1px solid #b3b3b3;
890 891 border-right:1px solid #eaeaea;
891 892 border-bottom:1px solid #eaeaea;
892 893 color:#000;
893 894 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
894 895 font-size:11px;
895 896 margin:0;
896 897 padding:7px 7px 6px;
897 898 }
898 899
899 900
900 901
901 902 #content div.box div.form div.fields div.field div.input input.small {
902 903 width:30%;
903 904 }
904 905
905 906 #content div.box div.form div.fields div.field div.input input.medium {
906 907 width:55%;
907 908 }
908 909
909 910 #content div.box div.form div.fields div.field div.input input.large {
910 911 width:85%;
911 912 }
912 913
913 914 #content div.box div.form div.fields div.field div.input input.date {
914 915 width:177px;
915 916 }
916 917
917 918 #content div.box div.form div.fields div.field div.input input.button {
918 919 background:#D4D0C8;
919 920 border-top:1px solid #FFF;
920 921 border-left:1px solid #FFF;
921 922 border-right:1px solid #404040;
922 923 border-bottom:1px solid #404040;
923 924 color:#000;
924 925 margin:0;
925 926 padding:4px 8px;
926 927 }
927 928
928 929 #content div.box div.form div.fields div.field div.textarea {
929 930 border-top:1px solid #b3b3b3;
930 931 border-left:1px solid #b3b3b3;
931 932 border-right:1px solid #eaeaea;
932 933 border-bottom:1px solid #eaeaea;
933 934 margin:0 0 0 200px;
934 935 padding:10px;
935 936 }
936 937
937 938 #content div.box div.form div.fields div.field div.textarea-editor {
938 939 border:1px solid #ddd;
939 940 padding:0;
940 941 }
941 942
942 943 #content div.box div.form div.fields div.field div.textarea textarea {
943 944 width:100%;
944 945 height:220px;
945 946 overflow:hidden;
946 947 background:#FFF;
947 948 color:#000;
948 949 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
949 950 font-size:11px;
950 951 outline:none;
951 952 border-width:0;
952 953 margin:0;
953 954 padding:0;
954 955 }
955 956
956 957 #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 {
957 958 width:100%;
958 959 height:100px;
959 960 }
960 961
961 962 #content div.box div.form div.fields div.field div.textarea table {
962 963 width:100%;
963 964 border:none;
964 965 margin:0;
965 966 padding:0;
966 967 }
967 968
968 969 #content div.box div.form div.fields div.field div.textarea table td {
969 970 background:#DDD;
970 971 border:none;
971 972 padding:0;
972 973 }
973 974
974 975 #content div.box div.form div.fields div.field div.textarea table td table {
975 976 width:auto;
976 977 border:none;
977 978 margin:0;
978 979 padding:0;
979 980 }
980 981
981 982 #content div.box div.form div.fields div.field div.textarea table td table td {
982 983 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
983 984 font-size:11px;
984 985 padding:5px 5px 5px 0;
985 986 }
986 987
987 988 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
988 989 background:#f6f6f6;
989 990 border-color:#666;
990 991 }
991 992
992 993 div.form div.fields div.field div.button {
993 994 margin:0;
994 995 padding:0 0 0 8px;
995 996 }
996 997
997 998 div.form div.fields div.field div.highlight .ui-button {
998 999 background:#4e85bb url("../images/button_highlight.png") repeat-x;
999 1000 border-top:1px solid #5c91a4;
1000 1001 border-left:1px solid #2a6f89;
1001 1002 border-right:1px solid #2b7089;
1002 1003 border-bottom:1px solid #1a6480;
1003 1004 color:#FFF;
1004 1005 margin:0;
1005 1006 padding:6px 12px;
1006 1007 }
1007 1008
1008 1009 div.form div.fields div.field div.highlight .ui-state-hover {
1009 1010 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1010 1011 border-top:1px solid #78acbf;
1011 1012 border-left:1px solid #34819e;
1012 1013 border-right:1px solid #35829f;
1013 1014 border-bottom:1px solid #257897;
1014 1015 color:#FFF;
1015 1016 margin:0;
1016 1017 padding:6px 12px;
1017 1018 }
1018 1019
1019 1020 #content div.box div.form div.fields div.buttons div.highlight input.ui-button {
1020 1021 background:#4e85bb url("../images/button_highlight.png") repeat-x;
1021 1022 border-top:1px solid #5c91a4;
1022 1023 border-left:1px solid #2a6f89;
1023 1024 border-right:1px solid #2b7089;
1024 1025 border-bottom:1px solid #1a6480;
1025 1026 color:#fff;
1026 1027 margin:0;
1027 1028 padding:6px 12px;
1028 1029 }
1029 1030
1030 1031 #content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover {
1031 1032 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1032 1033 border-top:1px solid #78acbf;
1033 1034 border-left:1px solid #34819e;
1034 1035 border-right:1px solid #35829f;
1035 1036 border-bottom:1px solid #257897;
1036 1037 color:#fff;
1037 1038 margin:0;
1038 1039 padding:6px 12px;
1039 1040 }
1040 1041
1041 1042 #content div.box table {
1042 1043 width:100%;
1043 1044 border-collapse:collapse;
1044 1045 margin:0;
1045 1046 padding:0;
1046 1047 }
1047 1048
1048 1049 #content div.box table th {
1049 1050 background:#eee;
1050 1051 border-bottom:1px solid #ddd;
1051 1052 padding:5px 0px 5px 5px;
1052 1053 }
1053 1054
1054 1055 #content div.box table th.left {
1055 1056 text-align:left;
1056 1057 }
1057 1058
1058 1059 #content div.box table th.right {
1059 1060 text-align:right;
1060 1061 }
1061 1062
1062 1063 #content div.box table th.center {
1063 1064 text-align:center;
1064 1065 }
1065 1066
1066 1067 #content div.box table th.selected {
1067 1068 vertical-align:middle;
1068 1069 padding:0;
1069 1070 }
1070 1071
1071 1072 #content div.box table td {
1072 1073 background:#fff;
1073 1074 border-bottom:1px solid #cdcdcd;
1074 1075 vertical-align:middle;
1075 1076 padding:5px;
1076 1077 }
1077 1078
1078 1079 #content div.box table tr.selected td {
1079 1080 background:#FFC;
1080 1081 }
1081 1082
1082 1083 #content div.box table td.selected {
1083 1084 width:3%;
1084 1085 text-align:center;
1085 1086 vertical-align:middle;
1086 1087 padding:0;
1087 1088 }
1088 1089
1089 1090 #content div.box table td.action {
1090 1091 width:45%;
1091 1092 text-align:left;
1092 1093 }
1093 1094
1094 1095 #content div.box table td.date {
1095 1096 width:33%;
1096 1097 text-align:center;
1097 1098 }
1098 1099
1099 1100 #content div.box div.action {
1100 1101 float:right;
1101 1102 background:#FFF;
1102 1103 text-align:right;
1103 1104 margin:10px 0 0;
1104 1105 padding:0;
1105 1106 }
1106 1107
1107 1108 #content div.box div.action select {
1108 1109 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1109 1110 font-size:11px;
1110 1111 margin:0;
1111 1112 }
1112 1113
1113 1114 #content div.box div.action .ui-selectmenu {
1114 1115 margin:0;
1115 1116 padding:0;
1116 1117 }
1117 1118
1118 1119 #content div.box div.pagination {
1119 1120 height:1%;
1120 1121 clear:both;
1121 1122 overflow:hidden;
1122 1123 margin:10px 0 0;
1123 1124 padding:0;
1124 1125 }
1125 1126
1126 1127 #content div.box div.pagination ul.pager {
1127 1128 float:right;
1128 1129 text-align:right;
1129 1130 margin:0;
1130 1131 padding:0;
1131 1132 }
1132 1133
1133 1134 #content div.box div.pagination ul.pager li {
1134 1135 height:1%;
1135 1136 float:left;
1136 1137 list-style:none;
1137 1138 background:#ebebeb url("../images/pager.png") repeat-x;
1138 1139 border-top:1px solid #dedede;
1139 1140 border-left:1px solid #cfcfcf;
1140 1141 border-right:1px solid #c4c4c4;
1141 1142 border-bottom:1px solid #c4c4c4;
1142 1143 color:#4A4A4A;
1143 1144 font-weight:700;
1144 1145 margin:0 0 0 4px;
1145 1146 padding:0;
1146 1147 }
1147 1148
1148 1149 #content div.box div.pagination ul.pager li.separator {
1149 1150 padding:6px;
1150 1151 }
1151 1152
1152 1153 #content div.box div.pagination ul.pager li.current {
1153 1154 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1154 1155 border-top:1px solid #ccc;
1155 1156 border-left:1px solid #bebebe;
1156 1157 border-right:1px solid #b1b1b1;
1157 1158 border-bottom:1px solid #afafaf;
1158 1159 color:#515151;
1159 1160 padding:6px;
1160 1161 }
1161 1162
1162 1163 #content div.box div.pagination ul.pager li a {
1163 1164 height:1%;
1164 1165 display:block;
1165 1166 float:left;
1166 1167 color:#515151;
1167 1168 text-decoration:none;
1168 1169 margin:0;
1169 1170 padding:6px;
1170 1171 }
1171 1172
1172 1173 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1173 1174 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1174 1175 border-top:1px solid #ccc;
1175 1176 border-left:1px solid #bebebe;
1176 1177 border-right:1px solid #b1b1b1;
1177 1178 border-bottom:1px solid #afafaf;
1178 1179 margin:-1px;
1179 1180 }
1180 1181
1181 1182 #content div.box div.pagination-wh {
1182 1183 height:1%;
1183 1184 clear:both;
1184 1185 overflow:hidden;
1185 1186 text-align:right;
1186 1187 margin:10px 0 0;
1187 1188 padding:0;
1188 1189 }
1189 1190
1190 1191 #content div.box div.pagination-right {
1191 1192 float:right;
1192 1193 }
1193 1194
1194 1195 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1195 1196 height:1%;
1196 1197 float:left;
1197 1198 background:#ebebeb url("../images/pager.png") repeat-x;
1198 1199 border-top:1px solid #dedede;
1199 1200 border-left:1px solid #cfcfcf;
1200 1201 border-right:1px solid #c4c4c4;
1201 1202 border-bottom:1px solid #c4c4c4;
1202 1203 color:#4A4A4A;
1203 1204 font-weight:700;
1204 1205 margin:0 0 0 4px;
1205 1206 padding:6px;
1206 1207 }
1207 1208
1208 1209 #content div.box div.pagination-wh span.pager_curpage {
1209 1210 height:1%;
1210 1211 float:left;
1211 1212 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1212 1213 border-top:1px solid #ccc;
1213 1214 border-left:1px solid #bebebe;
1214 1215 border-right:1px solid #b1b1b1;
1215 1216 border-bottom:1px solid #afafaf;
1216 1217 color:#515151;
1217 1218 font-weight:700;
1218 1219 margin:0 0 0 4px;
1219 1220 padding:6px;
1220 1221 }
1221 1222
1222 1223 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1223 1224 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1224 1225 border-top:1px solid #ccc;
1225 1226 border-left:1px solid #bebebe;
1226 1227 border-right:1px solid #b1b1b1;
1227 1228 border-bottom:1px solid #afafaf;
1228 1229 text-decoration:none;
1229 1230 }
1230 1231
1231 1232 #content div.box div.traffic div.legend {
1232 1233 clear:both;
1233 1234 overflow:hidden;
1234 1235 border-bottom:1px solid #ddd;
1235 1236 margin:0 0 10px;
1236 1237 padding:0 0 10px;
1237 1238 }
1238 1239
1239 1240 #content div.box div.traffic div.legend h6 {
1240 1241 float:left;
1241 1242 border:none;
1242 1243 margin:0;
1243 1244 padding:0;
1244 1245 }
1245 1246
1246 1247 #content div.box div.traffic div.legend li {
1247 1248 list-style:none;
1248 1249 float:left;
1249 1250 font-size:11px;
1250 1251 margin:0;
1251 1252 padding:0 8px 0 4px;
1252 1253 }
1253 1254
1254 1255 #content div.box div.traffic div.legend li.visits {
1255 1256 border-left:12px solid #edc240;
1256 1257 }
1257 1258
1258 1259 #content div.box div.traffic div.legend li.pageviews {
1259 1260 border-left:12px solid #afd8f8;
1260 1261 }
1261 1262
1262 1263 #content div.box div.traffic table {
1263 1264 width:auto;
1264 1265 }
1265 1266
1266 1267 #content div.box div.traffic table td {
1267 1268 background:transparent;
1268 1269 border:none;
1269 1270 padding:2px 3px 3px;
1270 1271 }
1271 1272
1272 1273 #content div.box div.traffic table td.legendLabel {
1273 1274 padding:0 3px 2px;
1274 1275 }
1275 1276
1276 1277 #summary{
1277 1278
1278 1279 }
1279 1280
1280 1281 #summary .desc{
1281 1282 white-space: pre;
1282 1283 width: 100%;
1283 1284 }
1284 1285
1285 1286 #summary .repo_name{
1286 1287 font-size: 1.6em;
1287 1288 font-weight: bold;
1288 1289 vertical-align: baseline;
1289 1290 clear:right
1290 1291 }
1291 1292
1292 1293
1293 1294 #footer {
1294 1295 clear:both;
1295 1296 overflow:hidden;
1296 1297 text-align:right;
1297 1298 margin:0;
1298 1299 padding:0 10px 4px;
1299 1300 margin:-10px 0 0;
1300 1301 }
1301 1302
1302 1303 #footer div#footer-inner {
1303 1304 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1304 1305 border-top:2px solid #FFFFFF;
1305 1306 }
1306 1307
1307 1308 #footer div#footer-inner p {
1308 1309 padding:15px 25px 15px 0;
1309 1310 color:#FFF;
1310 1311 font-weight:700;
1311 1312 }
1312 1313 #footer div#footer-inner .footer-link {
1313 1314 float:left;
1314 1315 padding-left:10px;
1315 1316 }
1316 1317 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1317 1318 color:#FFF;
1318 1319 }
1319 1320
1320 1321 #login div.title {
1321 1322 width:420px;
1322 1323 clear:both;
1323 1324 overflow:hidden;
1324 1325 position:relative;
1325 1326 background:#003367 url("../images/header_inner.png") repeat-x;
1326 1327 margin:0 auto;
1327 1328 padding:0;
1328 1329 }
1329 1330
1330 1331 #login div.inner {
1331 1332 width:380px;
1332 1333 background:#FFF url("../images/login.png") no-repeat top left;
1333 1334 border-top:none;
1334 1335 border-bottom:none;
1335 1336 margin:0 auto;
1336 1337 padding:20px;
1337 1338 }
1338 1339
1339 1340 #login div.form div.fields div.field div.label {
1340 1341 width:173px;
1341 1342 float:left;
1342 1343 text-align:right;
1343 1344 margin:2px 10px 0 0;
1344 1345 padding:5px 0 0 5px;
1345 1346 }
1346 1347
1347 1348 #login div.form div.fields div.field div.input input {
1348 1349 width:176px;
1349 1350 background:#FFF;
1350 1351 border-top:1px solid #b3b3b3;
1351 1352 border-left:1px solid #b3b3b3;
1352 1353 border-right:1px solid #eaeaea;
1353 1354 border-bottom:1px solid #eaeaea;
1354 1355 color:#000;
1355 1356 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1356 1357 font-size:11px;
1357 1358 margin:0;
1358 1359 padding:7px 7px 6px;
1359 1360 }
1360 1361
1361 1362 #login div.form div.fields div.buttons {
1362 1363 clear:both;
1363 1364 overflow:hidden;
1364 1365 border-top:1px solid #DDD;
1365 1366 text-align:right;
1366 1367 margin:0;
1367 1368 padding:10px 0 0;
1368 1369 }
1369 1370
1370 1371 #login div.form div.links {
1371 1372 clear:both;
1372 1373 overflow:hidden;
1373 1374 margin:10px 0 0;
1374 1375 padding:0 0 2px;
1375 1376 }
1376 1377
1377 1378 #quick_login{
1378 1379 top: 31px;
1379 1380 background-color: rgb(0, 51, 103);
1380 1381 z-index: 999;
1381 1382 height: 150px;
1382 1383 position: absolute;
1383 1384 margin-left: -16px;
1384 1385 width: 281px;
1385 1386 border-radius: 0 0 8px 8px;
1387 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1388 }
1389
1390 #quick_login .password_forgoten{
1391 padding-right:10px;
1392 padding-top:10px;
1393 float:left;
1386 1394 }
1387 1395
1388 1396 #quick_login div.form div.fields{
1389 1397 padding-top: 2px;
1390 1398 padding-left:10px;
1391 1399 }
1392 1400
1393 1401 #quick_login div.form div.fields div.field{
1394 1402 padding: 5px;
1395 1403 }
1396 1404
1397 1405 #quick_login div.form div.fields div.field div.label label{
1398 1406 color:#fff;
1399 1407 padding-bottom: 3px;
1400 1408 }
1401 1409
1402 1410 #quick_login div.form div.fields div.field div.input input {
1403 1411 width:236px;
1404 1412 background:#FFF;
1405 1413 border-top:1px solid #b3b3b3;
1406 1414 border-left:1px solid #b3b3b3;
1407 1415 border-right:1px solid #eaeaea;
1408 1416 border-bottom:1px solid #eaeaea;
1409 1417 color:#000;
1410 1418 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1411 1419 font-size:11px;
1412 1420 margin:0;
1413 1421 padding:5px 7px 4px;
1414 1422 }
1415 1423
1416 1424 #quick_login div.form div.fields div.buttons {
1417 1425 clear:both;
1418 1426 overflow:hidden;
1419 1427 text-align:right;
1420 1428 margin:0;
1421 1429 padding:10px 14px 0;
1422 1430 }
1423 1431
1424 1432 #quick_login div.form div.fields div.buttons input.ui-button{
1425 1433 background:#e5e3e3 url("../images/button.png") repeat-x;
1426 1434 border-top:1px solid #DDD;
1427 1435 border-left:1px solid #c6c6c6;
1428 1436 border-right:1px solid #DDD;
1429 1437 border-bottom:1px solid #c6c6c6;
1430 1438 color:#515151;
1431 1439 margin:0;
1432 1440 padding:4px 10px;
1433 1441 }
1434 1442
1435 1443 #quick_login div.form div.links {
1436 1444 clear:both;
1437 1445 overflow:hidden;
1438 1446 margin:10px 0 0;
1439 1447 padding:0 0 2px;
1440 1448 }
1441 1449
1442 1450 #register div.title {
1443 1451 clear:both;
1444 1452 overflow:hidden;
1445 1453 position:relative;
1446 1454 background:#003367 url("../images/header_inner.png") repeat-x;
1447 1455 margin:0 auto;
1448 1456 padding:0;
1449 1457 }
1450 1458
1451 1459 #register div.inner {
1452 1460 background:#FFF;
1453 1461 border-top:none;
1454 1462 border-bottom:none;
1455 1463 margin:0 auto;
1456 1464 padding:20px;
1457 1465 }
1458 1466
1459 1467 #register div.form div.fields div.field div.label {
1460 1468 width:135px;
1461 1469 float:left;
1462 1470 text-align:right;
1463 1471 margin:2px 10px 0 0;
1464 1472 padding:5px 0 0 5px;
1465 1473 }
1466 1474
1467 1475 #register div.form div.fields div.field div.input input {
1468 1476 width:300px;
1469 1477 background:#FFF;
1470 1478 border-top:1px solid #b3b3b3;
1471 1479 border-left:1px solid #b3b3b3;
1472 1480 border-right:1px solid #eaeaea;
1473 1481 border-bottom:1px solid #eaeaea;
1474 1482 color:#000;
1475 1483 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1476 1484 font-size:11px;
1477 1485 margin:0;
1478 1486 padding:7px 7px 6px;
1479 1487 }
1480 1488
1481 1489 #register div.form div.fields div.buttons {
1482 1490 clear:both;
1483 1491 overflow:hidden;
1484 1492 border-top:1px solid #DDD;
1485 1493 text-align:left;
1486 1494 margin:0;
1487 1495 padding:10px 0 0 150px;
1488 1496 }
1489 1497
1490 1498 #register div.form div.fields div.buttons div.highlight input.ui-button {
1491 1499 background:url("../images/button_highlight.png") repeat-x scroll 0 0 #4E85BB;
1492 1500 color:#FFF;
1493 1501 border-color:#5C91A4 #2B7089 #1A6480 #2A6F89;
1494 1502 border-style:solid;
1495 1503 border-width:1px;
1496 1504 }
1497 1505
1498 1506 #register div.form div.activation_msg {
1499 1507 padding-top:4px;
1500 1508 padding-bottom:4px;
1501 1509 }
1502 1510
1503 1511 #journal .journal_day{
1504 1512 font-size:20px;
1505 1513 padding:10px 0px;
1506 1514 border-bottom:2px solid #DDD;
1507 1515 margin-left:10px;
1508 1516 margin-right:10px;
1509 1517 }
1510 1518
1511 1519 #journal .journal_container{
1512 1520 padding:5px;
1513 1521 clear:both;
1514 1522 margin:0px 5px 0px 10px;
1515 1523 }
1516 1524
1517 1525 #journal .journal_action_container{
1518 1526 padding-left:38px;
1519 1527 }
1520 1528
1521 1529 #journal .journal_user{
1522 1530 color: #747474;
1523 1531 font-size: 14px;
1524 1532 font-weight: bold;
1525 1533 height: 30px;
1526 1534 }
1527 1535 #journal .journal_icon{
1528 1536 clear: both;
1529 1537 float: left;
1530 1538 padding-right: 4px;
1531 1539 padding-top: 3px;
1532 1540 }
1533 1541 #journal .journal_action{
1534 1542 padding-top:4px;
1535 1543 min-height:2px;
1536 1544 float:left
1537 1545 }
1538 1546 #journal .journal_action_params{
1539 1547 clear: left;
1540 1548 padding-left: 22px;
1541 1549 }
1542 1550 #journal .journal_repo{
1543 1551 float: left;
1544 1552 margin-left: 6px;
1545 1553 padding-top: 3px;
1546 1554 }
1547 1555 #journal .date{
1548 1556 clear: both;
1549 1557 color: #777777;
1550 1558 font-size: 11px;
1551 1559 padding-left: 22px;
1552 1560 }
1553 1561 #journal .journal_repo .journal_repo_name{
1554 1562 font-weight: bold;
1555 1563 font-size: 1.1em;
1556 1564 }
1557 1565 #journal .compare_view{
1558 1566 padding: 5px 0px 5px 0px;
1559 1567 width: 95px;
1560 1568 }
1561 1569 .journal_highlight{
1562 1570 font-weight: bold;
1563 1571 padding: 0 2px;
1564 1572 vertical-align: bottom;
1565 1573 }
1566 1574 .trending_language_tbl,.trending_language_tbl td {
1567 1575 border:0 !important;
1568 1576 margin:0 !important;
1569 1577 padding:0 !important;
1570 1578 }
1571 1579
1572 1580 .trending_language {
1573 1581 background-color:#003367;
1574 1582 color:#FFF;
1575 1583 display:block;
1576 1584 min-width:20px;
1577 1585 text-decoration:none;
1578 1586 height:12px;
1579 1587 margin-bottom:4px;
1580 1588 margin-left:5px;
1581 1589 white-space:pre;
1582 1590 padding:3px;
1583 1591 }
1584 1592
1585 1593 h3.files_location {
1586 1594 font-size:1.8em;
1587 1595 font-weight:700;
1588 1596 border-bottom:none !important;
1589 1597 margin:10px 0 !important;
1590 1598 }
1591 1599
1592 1600 #files_data dl dt {
1593 1601 float:left;
1594 1602 width:115px;
1595 1603 margin:0 !important;
1596 1604 padding:5px;
1597 1605 }
1598 1606
1599 1607 #files_data dl dd {
1600 1608 margin:0 !important;
1601 1609 padding:5px !important;
1602 1610 }
1603 1611
1604 1612 #changeset_content {
1605 1613 border:1px solid #CCC;
1606 1614 padding:5px;
1607 1615 }
1608 1616 #changeset_compare_view_content{
1609 1617 border:1px solid #CCC;
1610 1618 padding:5px;
1611 1619 }
1612 1620
1613 1621 #changeset_content .container {
1614 1622 min-height:120px;
1615 1623 font-size:1.2em;
1616 1624 overflow:hidden;
1617 1625 }
1618 1626
1619 1627 #changeset_compare_view_content .compare_view_commits{
1620 1628 width: auto !important;
1621 1629 }
1622 1630
1623 1631 #changeset_compare_view_content .compare_view_commits td{
1624 1632 padding:0px 0px 0px 12px !important;
1625 1633 }
1626 1634
1627 1635 #changeset_content .container .right {
1628 1636 float:right;
1629 1637 width:25%;
1630 1638 text-align:right;
1631 1639 }
1632 1640
1633 1641 #changeset_content .container .left .message {
1634 1642 font-style:italic;
1635 1643 color:#556CB5;
1636 1644 white-space:pre-wrap;
1637 1645 }
1638 1646
1639 1647 .cs_files .cur_cs{
1640 1648 margin:10px 2px;
1641 1649 font-weight: bold;
1642 1650 }
1643 1651
1644 1652 .cs_files .node{
1645 1653 float: left;
1646 1654 }
1647 1655 .cs_files .changes{
1648 1656 float: right;
1649 1657 }
1650 1658 .cs_files .changes .added{
1651 1659 background-color: #BBFFBB;
1652 1660 float: left;
1653 1661 text-align: center;
1654 1662 font-size: 90%;
1655 1663 }
1656 1664 .cs_files .changes .deleted{
1657 1665 background-color: #FF8888;
1658 1666 float: left;
1659 1667 text-align: center;
1660 1668 font-size: 90%;
1661 1669 }
1662 1670 .cs_files .cs_added {
1663 1671 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1664 1672 height:16px;
1665 1673 padding-left:20px;
1666 1674 margin-top:7px;
1667 1675 text-align:left;
1668 1676 }
1669 1677
1670 1678 .cs_files .cs_changed {
1671 1679 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1672 1680 height:16px;
1673 1681 padding-left:20px;
1674 1682 margin-top:7px;
1675 1683 text-align:left;
1676 1684 }
1677 1685
1678 1686 .cs_files .cs_removed {
1679 1687 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1680 1688 height:16px;
1681 1689 padding-left:20px;
1682 1690 margin-top:7px;
1683 1691 text-align:left;
1684 1692 }
1685 1693
1686 1694 #graph {
1687 1695 overflow:hidden;
1688 1696 }
1689 1697
1690 1698 #graph_nodes {
1691 1699 width:160px;
1692 1700 float:left;
1693 1701 margin-left:-50px;
1694 1702 margin-top:5px;
1695 1703 }
1696 1704
1697 1705 #graph_content {
1698 1706 width:800px;
1699 1707 float:left;
1700 1708 }
1701 1709
1702 1710 #graph_content .container_header {
1703 1711 border:1px solid #CCC;
1704 1712 padding:10px;
1705 1713 }
1706 1714 #graph_content #rev_range_container{
1707 1715 padding:10px 0px;
1708 1716 }
1709 1717 #graph_content .container {
1710 1718 border-bottom:1px solid #CCC;
1711 1719 border-left:1px solid #CCC;
1712 1720 border-right:1px solid #CCC;
1713 1721 min-height:80px;
1714 1722 overflow:hidden;
1715 1723 font-size:1.2em;
1716 1724 }
1717 1725
1718 1726 #graph_content .container .right {
1719 1727 float:right;
1720 1728 width:28%;
1721 1729 text-align:right;
1722 1730 padding-bottom:5px;
1723 1731 }
1724 1732
1725 1733 #graph_content .container .left .date {
1726 1734 font-weight:700;
1727 1735 padding-bottom:5px;
1728 1736 }
1729 1737 #graph_content .container .left .date span{
1730 1738 vertical-align: text-top;
1731 1739 }
1732 1740
1733 1741 #graph_content .container .left .message {
1734 1742 font-size:100%;
1735 1743 padding-top:3px;
1736 1744 white-space:pre-wrap;
1737 1745 }
1738 1746
1739 1747 .right div {
1740 1748 clear:both;
1741 1749 }
1742 1750
1743 1751 .right .changes .added,.changed,.removed {
1744 1752 border:1px solid #DDD;
1745 1753 display:block;
1746 1754 float:right;
1747 1755 text-align:center;
1748 1756 min-width:15px;
1749 1757 cursor: help;
1750 1758 }
1751 1759 .right .changes .large {
1752 1760 border:1px solid #DDD;
1753 1761 display:block;
1754 1762 float:right;
1755 1763 text-align:center;
1756 1764 min-width:45px;
1757 1765 cursor: help;
1758 1766 background: #54A9F7;
1759 1767 }
1760 1768
1761 1769 .right .changes .added {
1762 1770 background:#BFB;
1763 1771 }
1764 1772
1765 1773 .right .changes .changed {
1766 1774 background:#FD8;
1767 1775 }
1768 1776
1769 1777 .right .changes .removed {
1770 1778 background:#F88;
1771 1779 }
1772 1780
1773 1781 .right .merge {
1774 1782 vertical-align:top;
1775 1783 font-size:0.75em;
1776 1784 font-weight:700;
1777 1785 }
1778 1786
1779 1787 .right .parent {
1780 1788 font-size:90%;
1781 1789 font-family:monospace;
1782 1790 }
1783 1791
1784 1792 .right .logtags .branchtag {
1785 1793 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1786 1794 display:block;
1787 1795 font-size:0.8em;
1788 1796 padding:11px 16px 0 0;
1789 1797 }
1790 1798
1791 1799 .right .logtags .tagtag {
1792 1800 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1793 1801 display:block;
1794 1802 font-size:0.8em;
1795 1803 padding:11px 16px 0 0;
1796 1804 }
1797 1805
1798 1806 div.browserblock {
1799 1807 overflow:hidden;
1800 1808 border:1px solid #ccc;
1801 1809 background:#f8f8f8;
1802 1810 font-size:100%;
1803 1811 line-height:125%;
1804 1812 padding:0;
1805 1813 }
1806 1814
1807 1815 div.browserblock .browser-header {
1808 1816 background:#FFF;
1809 1817 padding:10px 0px 25px 0px;
1810 1818 width: 100%;
1811 1819 }
1812 1820 div.browserblock .browser-nav {
1813 1821 float:left
1814 1822 }
1815 1823
1816 1824 div.browserblock .browser-branch {
1817 1825 float:left;
1818 1826 }
1819 1827
1820 1828 div.browserblock .browser-branch label {
1821 1829 color:#4A4A4A;
1822 1830 vertical-align:text-top;
1823 1831 }
1824 1832
1825 1833 div.browserblock .browser-header span {
1826 1834 margin-left:5px;
1827 1835 font-weight:700;
1828 1836 }
1829 1837
1830 1838 div.browserblock .browser-body {
1831 1839 background:#EEE;
1832 1840 border-top:1px solid #CCC;
1833 1841 }
1834 1842
1835 1843 table.code-browser {
1836 1844 border-collapse:collapse;
1837 1845 width:100%;
1838 1846 }
1839 1847
1840 1848 table.code-browser tr {
1841 1849 margin:3px;
1842 1850 }
1843 1851
1844 1852 table.code-browser thead th {
1845 1853 background-color:#EEE;
1846 1854 height:20px;
1847 1855 font-size:1.1em;
1848 1856 font-weight:700;
1849 1857 text-align:left;
1850 1858 padding-left:10px;
1851 1859 }
1852 1860
1853 1861 table.code-browser tbody td {
1854 1862 padding-left:10px;
1855 1863 height:20px;
1856 1864 }
1857 1865
1858 1866 table.code-browser .browser-file {
1859 1867 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1860 1868 height:16px;
1861 1869 padding-left:20px;
1862 1870 text-align:left;
1863 1871 }
1864 1872 .diffblock .changeset_file{
1865 1873 background:url("../images/icons/file.png") no-repeat scroll 3px;
1866 1874 height:16px;
1867 1875 padding-left:22px;
1868 1876 text-align:left;
1869 1877 font-size: 14px;
1870 1878 }
1871 1879
1872 1880 .diffblock .changeset_header{
1873 1881 margin-left: 6px !important;
1874 1882 }
1875 1883
1876 1884 table.code-browser .browser-dir {
1877 1885 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1878 1886 height:16px;
1879 1887 padding-left:20px;
1880 1888 text-align:left;
1881 1889 }
1882 1890
1883 1891 .box .search {
1884 1892 clear:both;
1885 1893 overflow:hidden;
1886 1894 margin:0;
1887 1895 padding:0 20px 10px;
1888 1896 }
1889 1897
1890 1898 .box .search div.search_path {
1891 1899 background:none repeat scroll 0 0 #EEE;
1892 1900 border:1px solid #CCC;
1893 1901 color:blue;
1894 1902 margin-bottom:10px;
1895 1903 padding:10px 0;
1896 1904 }
1897 1905
1898 1906 .box .search div.search_path div.link {
1899 1907 font-weight:700;
1900 1908 margin-left:25px;
1901 1909 }
1902 1910
1903 1911 .box .search div.search_path div.link a {
1904 1912 color:#003367;
1905 1913 cursor:pointer;
1906 1914 text-decoration:none;
1907 1915 }
1908 1916
1909 1917 #path_unlock {
1910 1918 color:red;
1911 1919 font-size:1.2em;
1912 1920 padding-left:4px;
1913 1921 }
1914 1922
1915 1923 .info_box span {
1916 1924 margin-left:3px;
1917 1925 margin-right:3px;
1918 1926 }
1919 1927
1920 1928 .info_box .rev {
1921 1929 color: #003367;
1922 1930 font-size: 1.6em;
1923 1931 font-weight: bold;
1924 1932 vertical-align: sub;
1925 1933 }
1926 1934
1927 1935
1928 1936 .info_box input#at_rev,.info_box input#size {
1929 1937 background:#FFF;
1930 1938 border-top:1px solid #b3b3b3;
1931 1939 border-left:1px solid #b3b3b3;
1932 1940 border-right:1px solid #eaeaea;
1933 1941 border-bottom:1px solid #eaeaea;
1934 1942 color:#000;
1935 1943 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1936 1944 font-size:12px;
1937 1945 margin:0;
1938 1946 padding:1px 5px 1px;
1939 1947 }
1940 1948
1941 1949
1942 1950
1943 1951 .info_box input#view {
1944 1952 text-align:center;
1945 1953 padding:4px 3px 2px 2px;
1946 1954 }
1947 1955
1948 1956 .yui-overlay,.yui-panel-container {
1949 1957 visibility:hidden;
1950 1958 position:absolute;
1951 1959 z-index:2;
1952 1960 }
1953 1961
1954 1962 .yui-tt {
1955 1963 visibility:hidden;
1956 1964 position:absolute;
1957 1965 color:#666;
1958 1966 background-color:#FFF;
1959 1967 font-family:arial, helvetica, verdana, sans-serif;
1960 1968 border:2px solid #003367;
1961 1969 font:100% sans-serif;
1962 1970 width:auto;
1963 1971 opacity:1px;
1964 1972 padding:8px;
1965 1973 white-space: pre-wrap;
1966 1974 -webkit-border-radius: 8px 8px 8px 8px;
1967 1975 -khtml-border-radius: 8px 8px 8px 8px;
1968 1976 -moz-border-radius: 8px 8px 8px 8px;
1969 1977 border-radius: 8px 8px 8px 8px;
1970 1978
1971 1979 }
1972 1980
1973 1981 .ac {
1974 1982 vertical-align:top;
1975 1983 }
1976 1984
1977 1985 .ac .yui-ac {
1978 1986 position:relative;
1979 1987 font-family:arial;
1980 1988 font-size:100%;
1981 1989 }
1982 1990
1983 1991 .ac .perm_ac {
1984 1992 width:15em;
1985 1993 }
1986 1994
1987 1995 .ac .yui-ac-input {
1988 1996 width:100%;
1989 1997 }
1990 1998
1991 1999 .ac .yui-ac-container {
1992 2000 position:absolute;
1993 2001 top:1.6em;
1994 2002 width:100%;
1995 2003 }
1996 2004
1997 2005 .ac .yui-ac-content {
1998 2006 position:absolute;
1999 2007 width:100%;
2000 2008 border:1px solid gray;
2001 2009 background:#fff;
2002 2010 overflow:hidden;
2003 2011 z-index:9050;
2004 2012 }
2005 2013
2006 2014 .ac .yui-ac-shadow {
2007 2015 position:absolute;
2008 2016 width:100%;
2009 2017 background:#000;
2010 2018 -moz-opacity:0.1px;
2011 2019 opacity:.10;
2012 2020 filter:alpha(opacity = 10);
2013 2021 z-index:9049;
2014 2022 margin:.3em;
2015 2023 }
2016 2024
2017 2025 .ac .yui-ac-content ul {
2018 2026 width:100%;
2019 2027 margin:0;
2020 2028 padding:0;
2021 2029 }
2022 2030
2023 2031 .ac .yui-ac-content li {
2024 2032 cursor:default;
2025 2033 white-space:nowrap;
2026 2034 margin:0;
2027 2035 padding:2px 5px;
2028 2036 }
2029 2037
2030 2038 .ac .yui-ac-content li.yui-ac-prehighlight {
2031 2039 background:#B3D4FF;
2032 2040 }
2033 2041
2034 2042 .ac .yui-ac-content li.yui-ac-highlight {
2035 2043 background:#556CB5;
2036 2044 color:#FFF;
2037 2045 }
2038 2046
2039 2047
2040 2048 .follow{
2041 2049 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2042 2050 height: 16px;
2043 2051 width: 20px;
2044 2052 cursor: pointer;
2045 2053 display: block;
2046 2054 float: right;
2047 2055 margin-top: 2px;
2048 2056 }
2049 2057
2050 2058 .following{
2051 2059 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2052 2060 height: 16px;
2053 2061 width: 20px;
2054 2062 cursor: pointer;
2055 2063 display: block;
2056 2064 float: right;
2057 2065 margin-top: 2px;
2058 2066 }
2059 2067
2060 2068 .currently_following{
2061 2069 padding-left: 10px;
2062 2070 padding-bottom:5px;
2063 2071 }
2064 2072
2065 2073 .add_icon {
2066 2074 background:url("../images/icons/add.png") no-repeat scroll 3px;
2067 2075 padding-left:20px;
2068 2076 padding-top:0px;
2069 2077 text-align:left;
2070 2078 }
2071 2079
2072 2080 .edit_icon {
2073 2081 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2074 2082 padding-left:20px;
2075 2083 padding-top:0px;
2076 2084 text-align:left;
2077 2085 }
2078 2086
2079 2087 .delete_icon {
2080 2088 background:url("../images/icons/delete.png") no-repeat scroll 3px;
2081 2089 padding-left:20px;
2082 2090 padding-top:0px;
2083 2091 text-align:left;
2084 2092 }
2085 2093
2086 2094 .refresh_icon {
2087 2095 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
2088 2096 padding-left:20px;
2089 2097 padding-top:0px;
2090 2098 text-align:left;
2091 2099 }
2092 2100
2093 2101 .pull_icon {
2094 2102 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2095 2103 padding-left:20px;
2096 2104 padding-top:0px;
2097 2105 text-align:left;
2098 2106 }
2099 2107
2100 2108 .rss_icon {
2101 2109 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2102 2110 padding-left:20px;
2103 2111 padding-top:0px;
2104 2112 text-align:left;
2105 2113 }
2106 2114
2107 2115 .atom_icon {
2108 2116 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2109 2117 padding-left:20px;
2110 2118 padding-top:0px;
2111 2119 text-align:left;
2112 2120 }
2113 2121
2114 2122 .archive_icon {
2115 2123 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2116 2124 padding-left:20px;
2117 2125 text-align:left;
2118 2126 padding-top:1px;
2119 2127 }
2120 2128
2121 2129 .start_following_icon {
2122 2130 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2123 2131 padding-left:20px;
2124 2132 text-align:left;
2125 2133 padding-top:0px;
2126 2134 }
2127 2135
2128 2136 .stop_following_icon {
2129 2137 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2130 2138 padding-left:20px;
2131 2139 text-align:left;
2132 2140 padding-top:0px;
2133 2141 }
2134 2142
2135 2143 .action_button {
2136 2144 border:0;
2137 2145 display:inline;
2138 2146 }
2139 2147
2140 2148 .action_button:hover {
2141 2149 border:0;
2142 2150 text-decoration:underline;
2143 2151 cursor:pointer;
2144 2152 }
2145 2153
2146 2154 #switch_repos {
2147 2155 position:absolute;
2148 2156 height:25px;
2149 2157 z-index:1;
2150 2158 }
2151 2159
2152 2160 #switch_repos select {
2153 2161 min-width:150px;
2154 2162 max-height:250px;
2155 2163 z-index:1;
2156 2164 }
2157 2165
2158 2166 .breadcrumbs {
2159 2167 border:medium none;
2160 2168 color:#FFF;
2161 2169 float:left;
2162 2170 text-transform:uppercase;
2163 2171 font-weight:700;
2164 2172 font-size:14px;
2165 2173 margin:0;
2166 2174 padding:11px 0 11px 10px;
2167 2175 }
2168 2176
2169 2177 .breadcrumbs a {
2170 2178 color:#FFF;
2171 2179 }
2172 2180
2173 2181 .flash_msg ul {
2174 2182 margin:0;
2175 2183 padding:0 0 10px;
2176 2184 }
2177 2185
2178 2186 .error_msg {
2179 2187 background-color:#FFCFCF;
2180 2188 background-image:url("../images/icons/error_msg.png");
2181 2189 border:1px solid #FF9595;
2182 2190 color:#C30;
2183 2191 }
2184 2192
2185 2193 .warning_msg {
2186 2194 background-color:#FFFBCC;
2187 2195 background-image:url("../images/icons/warning_msg.png");
2188 2196 border:1px solid #FFF35E;
2189 2197 color:#C69E00;
2190 2198 }
2191 2199
2192 2200 .success_msg {
2193 2201 background-color:#D5FFCF;
2194 2202 background-image:url("../images/icons/success_msg.png");
2195 2203 border:1px solid #97FF88;
2196 2204 color:#090;
2197 2205 }
2198 2206
2199 2207 .notice_msg {
2200 2208 background-color:#DCE3FF;
2201 2209 background-image:url("../images/icons/notice_msg.png");
2202 2210 border:1px solid #93A8FF;
2203 2211 color:#556CB5;
2204 2212 }
2205 2213
2206 2214 .success_msg,.error_msg,.notice_msg,.warning_msg {
2207 2215 background-position:10px center;
2208 2216 background-repeat:no-repeat;
2209 2217 font-size:12px;
2210 2218 font-weight:700;
2211 2219 min-height:14px;
2212 2220 line-height:14px;
2213 2221 margin-bottom:0;
2214 2222 margin-top:0;
2215 2223 display:block;
2216 2224 overflow:auto;
2217 2225 padding:6px 10px 6px 40px;
2218 2226 }
2219 2227
2220 2228 #msg_close {
2221 2229 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2222 2230 cursor:pointer;
2223 2231 height:16px;
2224 2232 position:absolute;
2225 2233 right:5px;
2226 2234 top:5px;
2227 2235 width:16px;
2228 2236 }
2229 2237
2230 2238 div#legend_container table,div#legend_choices table {
2231 2239 width:auto !important;
2232 2240 }
2233 2241
2234 2242 table#permissions_manage {
2235 2243 width:0 !important;
2236 2244 }
2237 2245
2238 2246 table#permissions_manage span.private_repo_msg {
2239 2247 font-size:0.8em;
2240 2248 opacity:0.6px;
2241 2249 }
2242 2250
2243 2251 table#permissions_manage td.private_repo_msg {
2244 2252 font-size:0.8em;
2245 2253 }
2246 2254
2247 2255 table#permissions_manage tr#add_perm_input td {
2248 2256 vertical-align:middle;
2249 2257 }
2250 2258
2251 2259 div.gravatar {
2252 2260 background-color:#FFF;
2253 2261 border:1px solid #D0D0D0;
2254 2262 float:left;
2255 2263 margin-right:0.7em;
2256 2264 padding:2px 2px 0;
2257 2265 }
2258 2266
2259 2267 #header,#content,#footer {
2260 2268 min-width:978px;
2261 2269 }
2262 2270
2263 2271 #content {
2264 2272 min-height:100%;
2265 2273 clear:both;
2266 2274 overflow:hidden;
2267 2275 padding:14px 10px;
2268 2276 }
2269 2277
2270 2278 #content div.box div.title div.search {
2271 2279 background:url("../images/title_link.png") no-repeat top left;
2272 2280 border-left:1px solid #316293;
2273 2281 }
2274 2282
2275 2283 #content div.box div.title div.search div.input input {
2276 2284 border:1px solid #316293;
2277 2285 }
2278 2286
2279 2287 #content div.box div.title div.search div.button input.ui-button {
2280 2288 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2281 2289 border:1px solid #316293;
2282 2290 border-left:none;
2283 2291 color:#FFF;
2284 2292 }
2285 2293
2286 2294 #content div.box input.ui-button-small {
2287 2295 background:#e5e3e3 url("../images/button.png") repeat-x;
2288 2296 border-top:1px solid #DDD;
2289 2297 border-left:1px solid #c6c6c6;
2290 2298 border-right:1px solid #DDD;
2291 2299 border-bottom:1px solid #c6c6c6;
2292 2300 color:#515151;
2293 2301 outline:none;
2294 2302 margin:0;
2295 2303 }
2296 2304
2297 2305 #content div.box input.ui-button-small-blue {
2298 2306 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2299 2307 border-top:1px solid #5c91a4;
2300 2308 border-left:1px solid #2a6f89;
2301 2309 border-right:1px solid #2b7089;
2302 2310 border-bottom:1px solid #1a6480;
2303 2311 color:#fff;
2304 2312 }
2305 2313
2306 2314 #content div.box input.ui-button-small submit,button{
2307 2315 cursor: pointer;
2308 2316 }
2309 2317
2310 2318 #content div.box div.title div.search div.button input.ui-state-hover {
2311 2319 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2312 2320 border:1px solid #316293;
2313 2321 border-left:none;
2314 2322 color:#FFF;
2315 2323 }
2316 2324
2317 2325 #content div.box div.form div.fields div.field div.highlight .ui-button {
2318 2326 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2319 2327 border-top:1px solid #5c91a4;
2320 2328 border-left:1px solid #2a6f89;
2321 2329 border-right:1px solid #2b7089;
2322 2330 border-bottom:1px solid #1a6480;
2323 2331 color:#fff;
2324 2332 }
2325 2333
2326 2334 #content div.box div.form div.fields div.field div.highlight .ui-state-hover {
2327 2335 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2328 2336 border-top:1px solid #78acbf;
2329 2337 border-left:1px solid #34819e;
2330 2338 border-right:1px solid #35829f;
2331 2339 border-bottom:1px solid #257897;
2332 2340 color:#fff;
2333 2341 }
2334 2342
2335 2343 ins,div.options a:hover {
2336 2344 text-decoration:none;
2337 2345 }
2338 2346
2339 2347 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2340 2348 border:none;
2341 2349 }
2342 2350
2343 2351 img.icon,.right .merge img {
2344 2352 vertical-align:bottom;
2345 2353 }
2346 2354
2347 2355 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2348 2356 float:right;
2349 2357 margin:0;
2350 2358 padding:0;
2351 2359 }
2352 2360
2353 2361 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2354 2362 float:left;
2355 2363 }
2356 2364
2357 2365 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2358 2366 display:none;
2359 2367 }
2360 2368
2361 2369 #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 {
2362 2370 display:block;
2363 2371 }
2364 2372
2365 2373 #content div.graph{
2366 2374 padding:0 10px 10px;
2367 2375 }
2368 2376
2369 2377 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2370 2378 color:#bfe3ff;
2371 2379 }
2372 2380
2373 2381 #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 {
2374 2382 margin:10px 24px 10px 44px;
2375 2383 }
2376 2384
2377 2385 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
2378 2386 clear:both;
2379 2387 overflow:hidden;
2380 2388 margin:0;
2381 2389 padding:0 20px 10px;
2382 2390 }
2383 2391
2384 2392 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2385 2393 clear:both;
2386 2394 overflow:hidden;
2387 2395 margin:0;
2388 2396 padding:0;
2389 2397 }
2390 2398
2391 2399 #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 {
2392 2400 height:1%;
2393 2401 display:block;
2394 2402 color:#363636;
2395 2403 margin:0;
2396 2404 padding:2px 0 0;
2397 2405 }
2398 2406
2399 2407 #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 {
2400 2408 background:#FBE3E4;
2401 2409 border-top:1px solid #e1b2b3;
2402 2410 border-left:1px solid #e1b2b3;
2403 2411 border-right:1px solid #FBC2C4;
2404 2412 border-bottom:1px solid #FBC2C4;
2405 2413 }
2406 2414
2407 2415 #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 {
2408 2416 background:#E6EFC2;
2409 2417 border-top:1px solid #cebb98;
2410 2418 border-left:1px solid #cebb98;
2411 2419 border-right:1px solid #c6d880;
2412 2420 border-bottom:1px solid #c6d880;
2413 2421 }
2414 2422
2415 2423 #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 {
2416 2424 margin:0;
2417 2425 }
2418 2426
2419 2427 #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{
2420 2428 margin:0 0 0 0px !important;
2421 2429 padding:0;
2422 2430 }
2423 2431
2424 2432 #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 {
2425 2433 margin:0 0 0 200px;
2426 2434 padding:0;
2427 2435 }
2428 2436
2429 2437
2430 2438 #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 {
2431 2439 color:#000;
2432 2440 text-decoration:none;
2433 2441 }
2434 2442
2435 2443 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2436 2444 border:1px solid #666;
2437 2445 }
2438 2446
2439 2447 #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 {
2440 2448 clear:both;
2441 2449 overflow:hidden;
2442 2450 margin:0;
2443 2451 padding:8px 0 2px;
2444 2452 }
2445 2453
2446 2454 #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 {
2447 2455 float:left;
2448 2456 margin:0;
2449 2457 }
2450 2458
2451 2459 #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 {
2452 2460 height:1%;
2453 2461 display:block;
2454 2462 float:left;
2455 2463 margin:2px 0 0 4px;
2456 2464 }
2457 2465
2458 2466 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2459 2467 color:#000;
2460 2468 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2461 2469 font-size:11px;
2462 2470 font-weight:700;
2463 2471 margin:0;
2464 2472 }
2465 2473
2466 2474 div.form div.fields div.field div.button .ui-button,#content div.box div.form div.fields div.buttons input.ui-button {
2467 2475 background:#e5e3e3 url("../images/button.png") repeat-x;
2468 2476 border-top:1px solid #DDD;
2469 2477 border-left:1px solid #c6c6c6;
2470 2478 border-right:1px solid #DDD;
2471 2479 border-bottom:1px solid #c6c6c6;
2472 2480 color:#515151;
2473 2481 outline:none;
2474 2482 margin:0;
2475 2483 padding:6px 12px;
2476 2484 }
2477 2485
2478 2486 div.form div.fields div.field div.button .ui-state-hover,#content div.box div.form div.fields div.buttons input.ui-state-hover {
2479 2487 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2480 2488 border-top:1px solid #ccc;
2481 2489 border-left:1px solid #bebebe;
2482 2490 border-right:1px solid #b1b1b1;
2483 2491 border-bottom:1px solid #afafaf;
2484 2492 color:#515151;
2485 2493 outline:none;
2486 2494 margin:0;
2487 2495 padding:6px 12px;
2488 2496 }
2489 2497
2490 2498 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2491 2499 display:inline;
2492 2500 }
2493 2501
2494 2502 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2495 2503 margin:10px 0 0 200px;
2496 2504 padding:0;
2497 2505 }
2498 2506
2499 2507 #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 {
2500 2508 margin:10px 0 0;
2501 2509 }
2502 2510
2503 2511 #content div.box table td.user,#content div.box table td.address {
2504 2512 width:10%;
2505 2513 text-align:center;
2506 2514 }
2507 2515
2508 2516 #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 {
2509 2517 text-align:right;
2510 2518 margin:6px 0 0;
2511 2519 padding:0;
2512 2520 }
2513 2521
2514 2522 #content div.box div.action div.button input.ui-button,#login div.form div.fields div.buttons input.ui-button,#register div.form div.fields div.buttons input.ui-button {
2515 2523 background:#e5e3e3 url("../images/button.png") repeat-x;
2516 2524 border-top:1px solid #DDD;
2517 2525 border-left:1px solid #c6c6c6;
2518 2526 border-right:1px solid #DDD;
2519 2527 border-bottom:1px solid #c6c6c6;
2520 2528 color:#515151;
2521 2529 margin:0;
2522 2530 padding:6px 12px;
2523 2531 }
2524 2532
2525 2533 #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 {
2526 2534 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2527 2535 border-top:1px solid #ccc;
2528 2536 border-left:1px solid #bebebe;
2529 2537 border-right:1px solid #b1b1b1;
2530 2538 border-bottom:1px solid #afafaf;
2531 2539 color:#515151;
2532 2540 margin:0;
2533 2541 padding:6px 12px;
2534 2542 }
2535 2543
2536 2544 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2537 2545 text-align:left;
2538 2546 float:left;
2539 2547 margin:0;
2540 2548 padding:0;
2541 2549 }
2542 2550
2543 2551 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2544 2552 height:1%;
2545 2553 display:block;
2546 2554 float:left;
2547 2555 background:#ebebeb url("../images/pager.png") repeat-x;
2548 2556 border-top:1px solid #dedede;
2549 2557 border-left:1px solid #cfcfcf;
2550 2558 border-right:1px solid #c4c4c4;
2551 2559 border-bottom:1px solid #c4c4c4;
2552 2560 color:#4A4A4A;
2553 2561 font-weight:700;
2554 2562 margin:0;
2555 2563 padding:6px 8px;
2556 2564 }
2557 2565
2558 2566 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2559 2567 color:#B4B4B4;
2560 2568 padding:6px;
2561 2569 }
2562 2570
2563 2571 #login,#register {
2564 2572 width:520px;
2565 2573 margin:10% auto 0;
2566 2574 padding:0;
2567 2575 }
2568 2576
2569 2577 #login div.color,#register div.color {
2570 2578 clear:both;
2571 2579 overflow:hidden;
2572 2580 background:#FFF;
2573 2581 margin:10px auto 0;
2574 2582 padding:3px 3px 3px 0;
2575 2583 }
2576 2584
2577 2585 #login div.color a,#register div.color a {
2578 2586 width:20px;
2579 2587 height:20px;
2580 2588 display:block;
2581 2589 float:left;
2582 2590 margin:0 0 0 3px;
2583 2591 padding:0;
2584 2592 }
2585 2593
2586 2594 #login div.title h5,#register div.title h5 {
2587 2595 color:#fff;
2588 2596 margin:10px;
2589 2597 padding:0;
2590 2598 }
2591 2599
2592 2600 #login div.form div.fields div.field,#register div.form div.fields div.field {
2593 2601 clear:both;
2594 2602 overflow:hidden;
2595 2603 margin:0;
2596 2604 padding:0 0 10px;
2597 2605 }
2598 2606
2599 2607 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2600 2608 height:1%;
2601 2609 display:block;
2602 2610 color:red;
2603 2611 margin:8px 0 0;
2604 2612 padding:0;
2605 2613 max-width: 320px;
2606 2614 }
2607 2615
2608 2616 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2609 2617 color:#000;
2610 2618 font-weight:700;
2611 2619 }
2612 2620
2613 2621 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2614 2622 float:left;
2615 2623 margin:0;
2616 2624 padding:0;
2617 2625 }
2618 2626
2619 2627 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2620 2628 margin:0 0 0 184px;
2621 2629 padding:0;
2622 2630 }
2623 2631
2624 2632 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2625 2633 color:#565656;
2626 2634 font-weight:700;
2627 2635 }
2628 2636
2629 2637 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2630 2638 color:#000;
2631 2639 font-size:1em;
2632 2640 font-weight:700;
2633 2641 font-family:Verdana, Helvetica, Sans-Serif;
2634 2642 margin:0;
2635 2643 }
2636 2644
2637 2645 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2638 2646 width:600px;
2639 2647 }
2640 2648
2641 2649 #changeset_content .container .left,#graph_content .container .left {
2642 2650 float:left;
2643 2651 width:70%;
2644 2652 padding-left:5px;
2645 2653 }
2646 2654
2647 2655 #changeset_content .container .left .date,.ac .match {
2648 2656 font-weight:700;
2649 2657 padding-top: 5px;
2650 2658 padding-bottom:5px;
2651 2659 }
2652 2660
2653 2661 div#legend_container table td,div#legend_choices table td {
2654 2662 border:none !important;
2655 2663 height:20px !important;
2656 2664 padding:0 !important;
2657 2665 }
2658 2666
2659 2667 #q_filter{
2660 2668 border:0 none;
2661 2669 color:#AAAAAA;
2662 2670 margin-bottom:-4px;
2663 2671 margin-top:-4px;
2664 2672 padding-left:3px;
2665 2673 }
2666 2674
@@ -1,401 +1,401 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 6 <!-- user -->
7 7 <ul id="logged-user">
8 8 <li class="first">
9 9
10 10 <div id="quick_login" style="display:none">
11 11 ${h.form(h.url('login_home',came_from=h.url.current()))}
12 12 <div class="form">
13 13 <div class="fields">
14 14 <div class="field">
15 15 <div class="label">
16 16 <label for="username">${_('Username')}:</label>
17 17 </div>
18 18 <div class="input">
19 19 ${h.text('username',class_='focus',size=40)}
20 20 </div>
21 21
22 22 </div>
23 23 <div class="field">
24 24 <div class="label">
25 25 <label for="password">${_('Password')}:</label>
26 26 </div>
27 27 <div class="input">
28 28 ${h.password('password',class_='focus',size=40)}
29 29 </div>
30 30
31 31 </div>
32 32 <div class="buttons">
33 ${h.submit('sign_in','Sign In',class_="ui-button")}
33 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>${h.submit('sign_in','Sign In',class_="ui-button")}
34 34 </div>
35 35 </div>
36 36 </div>
37 37 ${h.end_form()}
38 38 <script type="text/javascript">
39 39 YUE.on('quick_login_link','click',function(e){
40 40
41 41 if(YUD.hasClass('quick_login_link','enabled')){
42 42 YUD.setStyle('quick_login','display','none');
43 43 YUD.removeClass('quick_login_link','enabled');
44 44 }
45 45 else{
46 46 YUD.setStyle('quick_login','display','');
47 47 YUD.addClass('quick_login_link','enabled');
48 48 YUD.get('username').focus();
49 49 }
50 50 //make sure we don't redirect
51 51 YUE.preventDefault(e);
52 52 });
53 53
54 54 </script>
55 55 </div>
56 56
57 57 <div class="gravatar">
58 58 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
59 59 </div>
60 60 <div class="account">
61 61 %if c.rhodecode_user.username == 'default':
62 62 <a href="${h.url('public_journal')}">${_('Public journal')}</a>
63 63 %else:
64 64 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
65 65 %endif
66 66 </div>
67 67 </li>
68 68 <li>
69 69 <a href="${h.url('home')}">${_('Home')}</a>
70 70 </li>
71 71 %if c.rhodecode_user.username != 'default':
72 72 <li>
73 73 <a href="${h.url('journal')}">${_('Journal')}</a>
74 74 ##(${c.unread_journal}
75 75 </li>
76 76 %endif
77 77 %if c.rhodecode_user.username == 'default':
78 78 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'),id='quick_login_link')}</li>
79 79 %else:
80 80 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
81 81 %endif
82 82 </ul>
83 83 <!-- end user -->
84 84 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
85 85 <div id="logo">
86 86 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
87 87 </div>
88 88 <!-- MENU -->
89 89 ${self.page_nav()}
90 90 <!-- END MENU -->
91 91 ${self.body()}
92 92 </div>
93 93 </div>
94 94 <!-- END HEADER -->
95 95
96 96 <!-- CONTENT -->
97 97 <div id="content">
98 98 <div class="flash_msg">
99 99 <% messages = h.flash.pop_messages() %>
100 100 % if messages:
101 101 <ul id="flash-messages">
102 102 % for message in messages:
103 103 <li class="${message.category}_msg">${message}</li>
104 104 % endfor
105 105 </ul>
106 106 % endif
107 107 </div>
108 108 <div id="main">
109 109 ${next.main()}
110 110 </div>
111 111 </div>
112 112 <!-- END CONTENT -->
113 113
114 114 <!-- FOOTER -->
115 115 <div id="footer">
116 116 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
117 117 <div>
118 118 <p class="footer-link">
119 119 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
120 120 </p>
121 121 <p class="footer-link-right">
122 122 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
123 123 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
124 124 </p>
125 125 </div>
126 126 </div>
127 127 <script type="text/javascript">
128 128 function tooltip_activate(){
129 129 ${h.tooltip.activate()}
130 130 }
131 131 tooltip_activate();
132 132 </script>
133 133 </div>
134 134 <!-- END FOOTER -->
135 135
136 136 ### MAKO DEFS ###
137 137 <%def name="page_nav()">
138 138 ${self.menu()}
139 139 </%def>
140 140
141 141 <%def name="breadcrumbs()">
142 142 <div class="breadcrumbs">
143 143 ${self.breadcrumbs_links()}
144 144 </div>
145 145 </%def>
146 146
147 147
148 148 <%def name="menu(current=None)">
149 149 <%
150 150 def is_current(selected):
151 151 if selected == current:
152 152 return h.literal('class="current"')
153 153 %>
154 154 %if current not in ['home','admin']:
155 155 ##REGULAR MENU
156 156 <ul id="quick">
157 157 <!-- repo switcher -->
158 158 <li>
159 159 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
160 160 <span class="icon">
161 161 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
162 162 </span>
163 163 <span>&darr;</span>
164 164 </a>
165 165 <ul id="repo_switcher_list" class="repo_switcher">
166 166 <li>
167 167 <a href="#">${_('loading...')}</a>
168 168 </li>
169 169 </ul>
170 170 <script type="text/javascript">
171 171 YUE.on('repo_switcher','mouseover',function(){
172 172 function qfilter(){
173 173 var S = YAHOO.util.Selector;
174 174
175 175 var q_filter = YUD.get('q_filter_rs');
176 176 var F = YAHOO.namespace('q_filter_rs');
177 177
178 178 YUE.on(q_filter,'click',function(){
179 179 q_filter.value = '';
180 180 });
181 181
182 182 F.filterTimeout = null;
183 183
184 184 F.updateFilter = function() {
185 185 // Reset timeout
186 186 F.filterTimeout = null;
187 187
188 188 var obsolete = [];
189 189 var nodes = S.query('ul#repo_switcher_list li a.repo_name');
190 190 var req = YUD.get('q_filter_rs').value;
191 191 for (n in nodes){
192 192 YUD.setStyle(nodes[n].parentNode,'display','')
193 193 }
194 194 if (req){
195 195 for (n in nodes){
196 196 console.log(n);
197 197 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
198 198 obsolete.push(nodes[n]);
199 199 }
200 200 }
201 201 if(obsolete){
202 202 for (n in obsolete){
203 203 YUD.setStyle(obsolete[n].parentNode,'display','none');
204 204 }
205 205 }
206 206 }
207 207 }
208 208
209 209 YUE.on(q_filter,'keyup',function(e){
210 210 clearTimeout(F.filterTimeout);
211 211 setTimeout(F.updateFilter,600);
212 212 });
213 213 }
214 214 var loaded = YUD.hasClass('repo_switcher','loaded');
215 215 if(!loaded){
216 216 YUD.addClass('repo_switcher','loaded');
217 217 YAHOO.util.Connect.asyncRequest('GET',"${h.url('repo_switcher')}",{
218 218 success:function(o){
219 219 YUD.get('repo_switcher_list').innerHTML = o.responseText;
220 220 qfilter();
221 221 },
222 222 failure:function(o){
223 223 YUD.removeClass('repo_switcher','loaded');
224 224 }
225 225 },null);
226 226 }
227 227 return false;
228 228 });
229 229 </script>
230 230 </li>
231 231
232 232 <li ${is_current('summary')}>
233 233 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
234 234 <span class="icon">
235 235 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
236 236 </span>
237 237 <span>${_('Summary')}</span>
238 238 </a>
239 239 </li>
240 240 ##<li ${is_current('shortlog')}>
241 241 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
242 242 ## <span class="icon">
243 243 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
244 244 ## </span>
245 245 ## <span>${_('Shortlog')}</span>
246 246 ## </a>
247 247 ##</li>
248 248 <li ${is_current('changelog')}>
249 249 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
250 250 <span class="icon">
251 251 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
252 252 </span>
253 253 <span>${_('Changelog')}</span>
254 254 </a>
255 255 </li>
256 256
257 257 <li ${is_current('switch_to')}>
258 258 <a title="${_('Switch to')}" href="#">
259 259 <span class="icon">
260 260 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
261 261 </span>
262 262 <span>${_('Switch to')}</span>
263 263 </a>
264 264 <ul>
265 265 <li>
266 266 ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
267 267 <ul>
268 268 %if c.rhodecode_repo.branches.values():
269 269 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
270 270 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
271 271 %endfor
272 272 %else:
273 273 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
274 274 %endif
275 275 </ul>
276 276 </li>
277 277 <li>
278 278 ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
279 279 <ul>
280 280 %if c.rhodecode_repo.tags.values():
281 281 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
282 282 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
283 283 %endfor
284 284 %else:
285 285 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
286 286 %endif
287 287 </ul>
288 288 </li>
289 289 </ul>
290 290 </li>
291 291 <li ${is_current('files')}>
292 292 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
293 293 <span class="icon">
294 294 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
295 295 </span>
296 296 <span>${_('Files')}</span>
297 297 </a>
298 298 </li>
299 299
300 300 <li ${is_current('options')}>
301 301 <a title="${_('Options')}" href="#">
302 302 <span class="icon">
303 303 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
304 304 </span>
305 305 <span>${_('Options')}</span>
306 306 </a>
307 307 <ul>
308 308 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
309 309 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
310 310 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
311 311 %else:
312 312 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
313 313 %endif
314 314 %endif
315 315 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
316 316 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
317 317
318 318 % if h.HasPermissionAll('hg.admin')('access admin main page'):
319 319 <li>
320 320 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
321 321 <%def name="admin_menu()">
322 322 <ul>
323 323 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
324 324 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
325 325 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
326 326 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
327 327 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
328 328 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
329 329 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
330 330 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
331 331 </ul>
332 332 </%def>
333 333
334 334 ${admin_menu()}
335 335 </li>
336 336 % endif
337 337 </ul>
338 338 </li>
339 339
340 340 <li>
341 341 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
342 342 <span class="icon_short">
343 343 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
344 344 </span>
345 345 <span id="current_followers_count" class="short">${c.repository_followers}</span>
346 346 </a>
347 347 </li>
348 348 <li>
349 349 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
350 350 <span class="icon_short">
351 351 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
352 352 </span>
353 353 <span class="short">${c.repository_forks}</span>
354 354 </a>
355 355 </li>
356 356
357 357 </ul>
358 358 %else:
359 359 ##ROOT MENU
360 360 <ul id="quick">
361 361 <li>
362 362 <a title="${_('Home')}" href="${h.url('home')}">
363 363 <span class="icon">
364 364 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
365 365 </span>
366 366 <span>${_('Home')}</span>
367 367 </a>
368 368 </li>
369 369 % if c.rhodecode_user.username != 'default':
370 370 <li>
371 371 <a title="${_('Journal')}" href="${h.url('journal')}">
372 372 <span class="icon">
373 373 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
374 374 </span>
375 375 <span>${_('Journal')}</span>
376 376 </a>
377 377 </li>
378 378 % endif
379 379 <li>
380 380 <a title="${_('Search')}" href="${h.url('search')}">
381 381 <span class="icon">
382 382 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
383 383 </span>
384 384 <span>${_('Search')}</span>
385 385 </a>
386 386 </li>
387 387
388 388 %if h.HasPermissionAll('hg.admin')('access admin main page'):
389 389 <li ${is_current('admin')}>
390 390 <a title="${_('Admin')}" href="${h.url('admin_home')}">
391 391 <span class="icon">
392 392 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
393 393 </span>
394 394 <span>${_('Admin')}</span>
395 395 </a>
396 396 ${admin_menu()}
397 397 </li>
398 398 %endif
399 399 </ul>
400 400 %endif
401 401 </%def> No newline at end of file
@@ -1,44 +1,44 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="base/root.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Reset You password')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <div id="register">
9 9
10 10 <div class="title top-left-rounded-corner top-right-rounded-corner">
11 11 <h5>${_('Reset You password to')} ${c.rhodecode_name}</h5>
12 12 </div>
13 13 <div class="inner">
14 14 ${h.form(url('password_reset'))}
15 15 <div class="form">
16 16 <!-- fields -->
17 17 <div class="fields">
18 18
19 19 <div class="field">
20 20 <div class="label">
21 21 <label for="email">${_('Email address')}:</label>
22 22 </div>
23 23 <div class="input">
24 24 ${h.text('email')}
25 25 </div>
26 26 </div>
27 27
28 28 <div class="buttons">
29 29 <div class="nohighlight">
30 30 ${h.submit('send','Reset my password',class_="ui-button")}
31 <div class="activation_msg">${_('Your new password will be send to matching email address')}</div>
31 <div class="activation_msg">${_('Password reset link will be send to matching email address')}</div>
32 32 </div>
33 33 </div>
34 34 </div>
35 35 </div>
36 36 ${h.end_form()}
37 37 <script type="text/javascript">
38 38 YUE.onDOMReady(function(){
39 39 YUD.get('email').focus();
40 40 })
41 41 </script>
42 42 </div>
43 43 </div>
44 44
@@ -1,230 +1,261 b''
1 1 # -*- coding: utf-8 -*-
2 2 from rhodecode.tests import *
3 3 from rhodecode.model.db import User
4 from rhodecode.lib import generate_api_key
4 5 from rhodecode.lib.auth import check_password
5 6
6 7
7 8 class TestLoginController(TestController):
8 9
9 10 def test_index(self):
10 11 response = self.app.get(url(controller='login', action='index'))
11 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
12 self.assertEqual(response.status, '200 OK')
12 13 # Test response...
13 14
14 15 def test_login_admin_ok(self):
15 16 response = self.app.post(url(controller='login', action='index'),
16 17 {'username':'test_admin',
17 18 'password':'test12'})
18 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
19 assert response.session['rhodecode_user'].username == 'test_admin', 'wrong logged in user'
19 self.assertEqual(response.status, '302 Found')
20 self.assertEqual(response.session['rhodecode_user'].username ,
21 'test_admin')
20 22 response = response.follow()
21 assert '%s repository' % HG_REPO in response.body
23 self.assertTrue('%s repository' % HG_REPO in response.body)
22 24
23 25 def test_login_regular_ok(self):
24 26 response = self.app.post(url(controller='login', action='index'),
25 27 {'username':'test_regular',
26 28 'password':'test12'})
27 print response
28 assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status
29 assert response.session['rhodecode_user'].username == 'test_regular', 'wrong logged in user'
29
30 self.assertEqual(response.status, '302 Found')
31 self.assertEqual(response.session['rhodecode_user'].username ,
32 'test_regular')
30 33 response = response.follow()
31 assert '%s repository' % HG_REPO in response.body
32 assert '<a title="Admin" href="/_admin">' not in response.body
34 self.assertTrue('%s repository' % HG_REPO in response.body)
35 self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
33 36
34 37 def test_login_ok_came_from(self):
35 38 test_came_from = '/_admin/users'
36 response = self.app.post(url(controller='login', action='index', came_from=test_came_from),
39 response = self.app.post(url(controller='login', action='index',
40 came_from=test_came_from),
37 41 {'username':'test_admin',
38 42 'password':'test12'})
39 assert response.status == '302 Found', 'Wrong response code from came from redirection'
43 self.assertEqual(response.status, '302 Found')
40 44 response = response.follow()
41 45
42 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
43 assert 'Users administration' in response.body, 'No proper title in response'
46 self.assertEqual(response.status, '200 OK')
47 self.assertTrue('Users administration' in response.body)
44 48
45 49
46 50 def test_login_short_password(self):
47 51 response = self.app.post(url(controller='login', action='index'),
48 52 {'username':'test_admin',
49 53 'password':'as'})
50 54 self.assertEqual(response.status, '200 OK')
51 print response.body
55
52 56 self.assertTrue('Enter 3 characters or more' in response.body)
53 57
54 58 def test_login_wrong_username_password(self):
55 59 response = self.app.post(url(controller='login', action='index'),
56 60 {'username':'error',
57 61 'password':'test12'})
58 assert response.status == '200 OK', 'Wrong response from login page'
62 self.assertEqual(response.status , '200 OK')
59 63
60 assert 'invalid user name' in response.body, 'No error username message in response'
61 assert 'invalid password' in response.body, 'No error password message in response'
64 self.assertTrue('invalid user name' in response.body)
65 self.assertTrue('invalid password' in response.body)
62 66
63 67 #==========================================================================
64 68 # REGISTRATIONS
65 69 #==========================================================================
66 70 def test_register(self):
67 71 response = self.app.get(url(controller='login', action='register'))
68 assert 'Sign Up to RhodeCode' in response.body, 'wrong page for user registration'
72 self.assertTrue('Sign Up to RhodeCode' in response.body)
69 73
70 74 def test_register_err_same_username(self):
71 75 response = self.app.post(url(controller='login', action='register'),
72 76 {'username':'test_admin',
73 77 'password':'test12',
74 78 'password_confirmation':'test12',
75 79 'email':'goodmail@domain.com',
76 80 'name':'test',
77 81 'lastname':'test'})
78 82
79 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
80 assert 'This username already exists' in response.body
83 self.assertEqual(response.status , '200 OK')
84 self.assertTrue('This username already exists' in response.body)
81 85
82 86 def test_register_err_same_email(self):
83 87 response = self.app.post(url(controller='login', action='register'),
84 88 {'username':'test_admin_0',
85 89 'password':'test12',
86 90 'password_confirmation':'test12',
87 91 'email':'test_admin@mail.com',
88 92 'name':'test',
89 93 'lastname':'test'})
90 94
91 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
95 self.assertEqual(response.status , '200 OK')
92 96 assert 'This e-mail address is already taken' in response.body
93 97
94 98 def test_register_err_same_email_case_sensitive(self):
95 99 response = self.app.post(url(controller='login', action='register'),
96 100 {'username':'test_admin_1',
97 101 'password':'test12',
98 102 'password_confirmation':'test12',
99 103 'email':'TesT_Admin@mail.COM',
100 104 'name':'test',
101 105 'lastname':'test'})
102 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
106 self.assertEqual(response.status , '200 OK')
103 107 assert 'This e-mail address is already taken' in response.body
104 108
105 109 def test_register_err_wrong_data(self):
106 110 response = self.app.post(url(controller='login', action='register'),
107 111 {'username':'xs',
108 112 'password':'test',
109 113 'password_confirmation':'test',
110 114 'email':'goodmailm',
111 115 'name':'test',
112 116 'lastname':'test'})
113 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
117 self.assertEqual(response.status , '200 OK')
114 118 assert 'An email address must contain a single @' in response.body
115 119 assert 'Enter a value 6 characters long or more' in response.body
116 120
117 121
118 122 def test_register_err_username(self):
119 123 response = self.app.post(url(controller='login', action='register'),
120 124 {'username':'error user',
121 125 'password':'test12',
122 126 'password_confirmation':'test12',
123 127 'email':'goodmailm',
124 128 'name':'test',
125 129 'lastname':'test'})
126 130
127 print response.body
128 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
131 self.assertEqual(response.status , '200 OK')
129 132 assert 'An email address must contain a single @' in response.body
130 133 assert ('Username may only contain '
131 134 'alphanumeric characters underscores, '
132 135 'periods or dashes and must begin with '
133 136 'alphanumeric character') in response.body
134 137
135 138 def test_register_err_case_sensitive(self):
136 139 response = self.app.post(url(controller='login', action='register'),
137 140 {'username':'Test_Admin',
138 141 'password':'test12',
139 142 'password_confirmation':'test12',
140 143 'email':'goodmailm',
141 144 'name':'test',
142 145 'lastname':'test'})
143 146
144 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
147 self.assertEqual(response.status , '200 OK')
145 148 assert 'An email address must contain a single @' in response.body
146 149 assert 'This username already exists' in response.body
147 150
148 151
149 152
150 153 def test_register_special_chars(self):
151 154 response = self.app.post(url(controller='login', action='register'),
152 155 {'username':'xxxaxn',
153 156 'password':'ąćźżąśśśś',
154 157 'password_confirmation':'ąćźżąśśśś',
155 158 'email':'goodmailm@test.plx',
156 159 'name':'test',
157 160 'lastname':'test'})
158 161
159 print response.body
160 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
162 self.assertEqual(response.status , '200 OK')
161 163 assert 'Invalid characters in password' in response.body
162 164
163 165
164 166 def test_register_password_mismatch(self):
165 167 response = self.app.post(url(controller='login', action='register'),
166 168 {'username':'xs',
167 169 'password':'123qwe',
168 170 'password_confirmation':'qwe123',
169 171 'email':'goodmailm@test.plxa',
170 172 'name':'test',
171 173 'lastname':'test'})
172 174
173 assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status
174 print response.body
175 self.assertEqual(response.status , '200 OK')
175 176 assert 'Password do not match' in response.body
176 177
177 178 def test_register_ok(self):
178 179 username = 'test_regular4'
179 180 password = 'qweqwe'
180 181 email = 'marcin@test.com'
181 182 name = 'testname'
182 183 lastname = 'testlastname'
183 184
184 185 response = self.app.post(url(controller='login', action='register'),
185 186 {'username':username,
186 187 'password':password,
187 188 'password_confirmation':password,
188 189 'email':email,
189 190 'name':name,
190 191 'lastname':lastname})
191 assert response.status == '302 Found', 'Wrong response from register page got %s' % response.status
192 self.assertEqual(response.status , '302 Found')
192 193 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
193 194
194 195 ret = self.sa.query(User).filter(User.username == 'test_regular4').one()
195 196 assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
196 197 assert check_password(password, ret.password) == True , 'password mismatch'
197 198 assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
198 199 assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
199 200 assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
200 201
201 202
202 203 def test_forgot_password_wrong_mail(self):
203 204 response = self.app.post(url(controller='login', action='password_reset'),
204 205 {'email':'marcin@wrongmail.org', })
205 206
206 207 assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
207 208
208 209 def test_forgot_password(self):
209 response = self.app.get(url(controller='login', action='password_reset'))
210 assert response.status == '200 OK', 'Wrong response from login page got %s' % response.status
210 response = self.app.get(url(controller='login',
211 action='password_reset'))
212 self.assertEqual(response.status , '200 OK')
211 213
212 214 username = 'test_password_reset_1'
213 215 password = 'qweqwe'
214 216 email = 'marcin@python-works.com'
215 217 name = 'passwd'
216 218 lastname = 'reset'
217 219
218 response = self.app.post(url(controller='login', action='register'),
219 {'username':username,
220 'password':password,
221 'password_confirmation':password,
222 'email':email,
223 'name':name,
224 'lastname':lastname})
225 #register new user for email test
226 response = self.app.post(url(controller='login', action='password_reset'),
227 {'email':email, })
228 print response.session['flash']
229 assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
230 assert 'Your new password was sent' in response.session['flash'][1], 'No flash message about password reset'
220 new = User()
221 new.username = username
222 new.password = password
223 new.email = email
224 new.name = name
225 new.lastname = lastname
226 new.api_key = generate_api_key(username)
227 self.sa.add(new)
228 self.sa.commit()
229
230 response = self.app.post(url(controller='login',
231 action='password_reset'),
232 {'email':email, })
233
234 self.checkSessionFlash(response, 'Your password reset link was sent')
235
236 response = response.follow()
237
238 # BAD KEY
239
240 key = "bad"
241 response = self.app.get(url(controller='login',
242 action='password_reset_confirmation',
243 key=key))
244 self.assertEqual(response.status, '302 Found')
245 self.assertTrue(response.location.endswith(url('reset_password')))
246
247 # GOOD KEY
248
249 key = User.by_username(username).api_key
250
251 response = self.app.get(url(controller='login',
252 action='password_reset_confirmation',
253 key=key))
254 self.assertEqual(response.status, '302 Found')
255 self.assertTrue(response.location.endswith(url('login_home')))
256
257 self.checkSessionFlash(response,
258 ('Your password reset was successful, '
259 'new password has been sent to your email'))
260
261 response = response.follow()
@@ -1,43 +1,43 b''
1 1 [egg_info]
2 2 tag_build = beta
3 3 tag_svn_revision = true
4 4
5 5 [easy_install]
6 6 find_links = http://www.pylonshq.com/download/
7 7
8 8 [nosetests]
9 verbose=False
9 verbose=True
10 10 verbosity=2
11 11 with-pylons=test.ini
12 detailed-errors=0
12 detailed-errors=1
13 13 nologcapture=1
14 14
15 15 # Babel configuration
16 16 [compile_catalog]
17 17 domain = rhodecode
18 18 directory = rhodecode/i18n
19 19 statistics = true
20 20
21 21 [extract_messages]
22 22 add_comments = TRANSLATORS:
23 23 output_file = rhodecode/i18n/rhodecode.pot
24 24 width = 80
25 25
26 26 [init_catalog]
27 27 domain = rhodecode
28 28 input_file = rhodecode/i18n/rhodecode.pot
29 29 output_dir = rhodecode/i18n
30 30
31 31 [update_catalog]
32 32 domain = rhodecode
33 33 input_file = rhodecode/i18n/rhodecode.pot
34 34 output_dir = rhodecode/i18n
35 35 previous = true
36 36
37 37 [build_sphinx]
38 38 source-dir = docs/
39 39 build-dir = docs/_build
40 40 all_files = 1
41 41
42 42 [upload_sphinx]
43 43 upload-dir = docs/_build/html No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now