##// END OF EJS Templates
merged + fixed pull request #62: Implemented metatags and visualisation options....
marcink -
r2674:a221706d beta
parent child Browse files
Show More
@@ -1,485 +1,486 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 43 from rhodecode.lib.helpers import get_token
44 44 from rhodecode.model.meta import Session
45 45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
46 46 from rhodecode.model.forms import RepoForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.repo import RepoModel
49 49 from rhodecode.lib.compat import json
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class ReposController(BaseController):
55 55 """
56 56 REST Controller styled on the Atom Publishing Protocol"""
57 57 # To properly map this controller, ensure your config/routing.py
58 58 # file has a resource setup:
59 59 # map.resource('repo', 'repos')
60 60
61 61 @LoginRequired()
62 62 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
63 63 def __before__(self):
64 64 c.admin_user = session.get('admin_user')
65 65 c.admin_username = session.get('admin_username')
66 66 super(ReposController, self).__before__()
67 67
68 68 def __load_defaults(self):
69 69 c.repo_groups = RepoGroup.groups_choices()
70 70 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
71 71
72 72 repo_model = RepoModel()
73 73 c.users_array = repo_model.get_users_js()
74 74 c.users_groups_array = repo_model.get_users_groups_js()
75 75 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
76 76 c.landing_revs_choices = choices
77 77
78 78 def __load_data(self, repo_name=None):
79 79 """
80 80 Load defaults settings for edit, and update
81 81
82 82 :param repo_name:
83 83 """
84 84 self.__load_defaults()
85 85
86 86 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
87 87 repo = db_repo.scm_instance
88 88
89 89 if c.repo_info is None:
90 90 h.flash(_('%s repository is not mapped to db perhaps'
91 91 ' it was created or renamed from the filesystem'
92 92 ' please run the application again'
93 93 ' in order to rescan repositories') % repo_name,
94 94 category='error')
95 95
96 96 return redirect(url('repos'))
97 97
98 98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 99 c.landing_revs_choices = choices
100 100
101 101 c.default_user_id = User.get_by_username('default').user_id
102 102 c.in_public_journal = UserFollowing.query()\
103 103 .filter(UserFollowing.user_id == c.default_user_id)\
104 104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105 105
106 106 if c.repo_info.stats:
107 107 # this is on what revision we ended up so we add +1 for count
108 108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 109 else:
110 110 last_rev = 0
111 111 c.stats_revision = last_rev
112 112
113 113 c.repo_last_rev = repo.count() if repo.revisions else 0
114 114
115 115 if last_rev == 0 or c.repo_last_rev == 0:
116 116 c.stats_percentage = 0
117 117 else:
118 118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 119 c.repo_last_rev) * 100)
120 120
121 121 defaults = RepoModel()._get_defaults(repo_name)
122 122
123 123 c.repos_list = [('', _('--REMOVE FORK--'))]
124 124 c.repos_list += [(x.repo_id, x.repo_name) for x in
125 125 Repository.query().order_by(Repository.repo_name).all()
126 126 if x.repo_id != c.repo_info.repo_id]
127 127
128 128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
129 129 return defaults
130 130
131 131 @HasPermissionAllDecorator('hg.admin')
132 132 def index(self, format='html'):
133 133 """GET /repos: All items in the collection"""
134 134 # url('repos')
135 135
136 136 c.repos_list = Repository.query()\
137 137 .order_by(Repository.repo_name)\
138 138 .all()
139 139
140 140 repos_data = []
141 141 total_records = len(c.repos_list)
142 142
143 143 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
144 144 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
145 145
146 146 quick_menu = lambda repo_name: (template.get_def("quick_menu")
147 .render(repo_name, _=_, h=h))
147 .render(repo_name, _=_, h=h, c=c))
148 148 repo_lnk = lambda name, rtype, private, fork_of: (
149 149 template.get_def("repo_name")
150 150 .render(name, rtype, private, fork_of, short_name=False,
151 admin=True, _=_, h=h))
151 admin=True, _=_, h=h, c=c))
152 152
153 153 repo_actions = lambda repo_name: (template.get_def("repo_actions")
154 .render(repo_name, _=_, h=h))
154 .render(repo_name, _=_, h=h, c=c))
155 155
156 156 for repo in c.repos_list:
157 157 repos_data.append({
158 158 "menu": quick_menu(repo.repo_name),
159 159 "raw_name": repo.repo_name,
160 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.private, repo.fork),
160 "name": repo_lnk(repo.repo_name, repo.repo_type,
161 repo.private, repo.fork),
161 162 "desc": repo.description,
162 163 "owner": repo.user.username,
163 164 "action": repo_actions(repo.repo_name),
164 165 })
165 166
166 167 c.data = json.dumps({
167 168 "totalRecords": total_records,
168 169 "startIndex": 0,
169 170 "sort": "name",
170 171 "dir": "asc",
171 172 "records": repos_data
172 173 })
173 174
174 175 return render('admin/repos/repos.html')
175 176
176 177 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
177 178 def create(self):
178 179 """
179 180 POST /repos: Create a new item"""
180 181 # url('repos')
181 182
182 183 self.__load_defaults()
183 184 form_result = {}
184 185 try:
185 186 form_result = RepoForm(repo_groups=c.repo_groups_choices,
186 187 landing_revs=c.landing_revs_choices)()\
187 188 .to_python(dict(request.POST))
188 189 new_repo = RepoModel().create(form_result,
189 190 self.rhodecode_user.user_id)
190 191 if form_result['clone_uri']:
191 192 h.flash(_('created repository %s from %s') \
192 193 % (form_result['repo_name'], form_result['clone_uri']),
193 194 category='success')
194 195 else:
195 196 h.flash(_('created repository %s') % form_result['repo_name'],
196 197 category='success')
197 198
198 199 if request.POST.get('user_created'):
199 200 # created by regular non admin user
200 201 action_logger(self.rhodecode_user, 'user_created_repo',
201 202 form_result['repo_name_full'], self.ip_addr,
202 203 self.sa)
203 204 else:
204 205 action_logger(self.rhodecode_user, 'admin_created_repo',
205 206 form_result['repo_name_full'], self.ip_addr,
206 207 self.sa)
207 208 Session().commit()
208 209 except formencode.Invalid, errors:
209 210
210 211 c.new_repo = errors.value['repo_name']
211 212
212 213 if request.POST.get('user_created'):
213 214 r = render('admin/repos/repo_add_create_repository.html')
214 215 else:
215 216 r = render('admin/repos/repo_add.html')
216 217
217 218 return htmlfill.render(
218 219 r,
219 220 defaults=errors.value,
220 221 errors=errors.error_dict or {},
221 222 prefix_error=False,
222 223 encoding="UTF-8")
223 224
224 225 except Exception:
225 226 log.error(traceback.format_exc())
226 227 msg = _('error occurred during creation of repository %s') \
227 228 % form_result.get('repo_name')
228 229 h.flash(msg, category='error')
229 230 #redirect to our new repo !
230 231 return redirect(url('summary_home', repo_name=new_repo.repo_name))
231 232
232 233 @HasPermissionAllDecorator('hg.admin')
233 234 def new(self, format='html'):
234 235 """GET /repos/new: Form to create a new item"""
235 236 new_repo = request.GET.get('repo', '')
236 237 c.new_repo = repo_name_slug(new_repo)
237 238 self.__load_defaults()
238 239 return render('admin/repos/repo_add.html')
239 240
240 241 @HasPermissionAllDecorator('hg.admin')
241 242 def update(self, repo_name):
242 243 """
243 244 PUT /repos/repo_name: Update an existing item"""
244 245 # Forms posted to this method should contain a hidden field:
245 246 # <input type="hidden" name="_method" value="PUT" />
246 247 # Or using helpers:
247 248 # h.form(url('repo', repo_name=ID),
248 249 # method='put')
249 250 # url('repo', repo_name=ID)
250 251 self.__load_defaults()
251 252 repo_model = RepoModel()
252 253 changed_name = repo_name
253 254 #override the choices with extracted revisions !
254 255 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
255 256 c.landing_revs_choices = choices
256 257
257 258 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
258 259 repo_groups=c.repo_groups_choices,
259 260 landing_revs=c.landing_revs_choices)()
260 261 try:
261 262 form_result = _form.to_python(dict(request.POST))
262 263 repo = repo_model.update(repo_name, form_result)
263 264 invalidate_cache('get_repo_cached_%s' % repo_name)
264 265 h.flash(_('Repository %s updated successfully') % repo_name,
265 266 category='success')
266 267 changed_name = repo.repo_name
267 268 action_logger(self.rhodecode_user, 'admin_updated_repo',
268 269 changed_name, self.ip_addr, self.sa)
269 270 Session().commit()
270 271 except formencode.Invalid, errors:
271 272 defaults = self.__load_data(repo_name)
272 273 defaults.update(errors.value)
273 274 return htmlfill.render(
274 275 render('admin/repos/repo_edit.html'),
275 276 defaults=defaults,
276 277 errors=errors.error_dict or {},
277 278 prefix_error=False,
278 279 encoding="UTF-8")
279 280
280 281 except Exception:
281 282 log.error(traceback.format_exc())
282 283 h.flash(_('error occurred during update of repository %s') \
283 284 % repo_name, category='error')
284 285 return redirect(url('edit_repo', repo_name=changed_name))
285 286
286 287 @HasPermissionAllDecorator('hg.admin')
287 288 def delete(self, repo_name):
288 289 """
289 290 DELETE /repos/repo_name: Delete an existing item"""
290 291 # Forms posted to this method should contain a hidden field:
291 292 # <input type="hidden" name="_method" value="DELETE" />
292 293 # Or using helpers:
293 294 # h.form(url('repo', repo_name=ID),
294 295 # method='delete')
295 296 # url('repo', repo_name=ID)
296 297
297 298 repo_model = RepoModel()
298 299 repo = repo_model.get_by_repo_name(repo_name)
299 300 if not repo:
300 301 h.flash(_('%s repository is not mapped to db perhaps'
301 302 ' it was moved or renamed from the filesystem'
302 303 ' please run the application again'
303 304 ' in order to rescan repositories') % repo_name,
304 305 category='error')
305 306
306 307 return redirect(url('repos'))
307 308 try:
308 309 action_logger(self.rhodecode_user, 'admin_deleted_repo',
309 310 repo_name, self.ip_addr, self.sa)
310 311 repo_model.delete(repo)
311 312 invalidate_cache('get_repo_cached_%s' % repo_name)
312 313 h.flash(_('deleted repository %s') % repo_name, category='success')
313 314 Session().commit()
314 315 except IntegrityError, e:
315 316 if e.message.find('repositories_fork_id_fkey') != -1:
316 317 log.error(traceback.format_exc())
317 318 h.flash(_('Cannot delete %s it still contains attached '
318 319 'forks') % repo_name,
319 320 category='warning')
320 321 else:
321 322 log.error(traceback.format_exc())
322 323 h.flash(_('An error occurred during '
323 324 'deletion of %s') % repo_name,
324 325 category='error')
325 326
326 327 except Exception, e:
327 328 log.error(traceback.format_exc())
328 329 h.flash(_('An error occurred during deletion of %s') % repo_name,
329 330 category='error')
330 331
331 332 return redirect(url('repos'))
332 333
333 334 @HasRepoPermissionAllDecorator('repository.admin')
334 335 def delete_perm_user(self, repo_name):
335 336 """
336 337 DELETE an existing repository permission user
337 338
338 339 :param repo_name:
339 340 """
340 341 try:
341 342 RepoModel().revoke_user_permission(repo=repo_name,
342 343 user=request.POST['user_id'])
343 344 Session().commit()
344 345 except Exception:
345 346 log.error(traceback.format_exc())
346 347 h.flash(_('An error occurred during deletion of repository user'),
347 348 category='error')
348 349 raise HTTPInternalServerError()
349 350
350 351 @HasRepoPermissionAllDecorator('repository.admin')
351 352 def delete_perm_users_group(self, repo_name):
352 353 """
353 354 DELETE an existing repository permission users group
354 355
355 356 :param repo_name:
356 357 """
357 358
358 359 try:
359 360 RepoModel().revoke_users_group_permission(
360 361 repo=repo_name, group_name=request.POST['users_group_id']
361 362 )
362 363 Session().commit()
363 364 except Exception:
364 365 log.error(traceback.format_exc())
365 366 h.flash(_('An error occurred during deletion of repository'
366 367 ' users groups'),
367 368 category='error')
368 369 raise HTTPInternalServerError()
369 370
370 371 @HasPermissionAllDecorator('hg.admin')
371 372 def repo_stats(self, repo_name):
372 373 """
373 374 DELETE an existing repository statistics
374 375
375 376 :param repo_name:
376 377 """
377 378
378 379 try:
379 380 RepoModel().delete_stats(repo_name)
380 381 Session().commit()
381 382 except Exception, e:
382 383 h.flash(_('An error occurred during deletion of repository stats'),
383 384 category='error')
384 385 return redirect(url('edit_repo', repo_name=repo_name))
385 386
386 387 @HasPermissionAllDecorator('hg.admin')
387 388 def repo_cache(self, repo_name):
388 389 """
389 390 INVALIDATE existing repository cache
390 391
391 392 :param repo_name:
392 393 """
393 394
394 395 try:
395 396 ScmModel().mark_for_invalidation(repo_name)
396 397 Session().commit()
397 398 except Exception, e:
398 399 h.flash(_('An error occurred during cache invalidation'),
399 400 category='error')
400 401 return redirect(url('edit_repo', repo_name=repo_name))
401 402
402 403 @HasPermissionAllDecorator('hg.admin')
403 404 def repo_public_journal(self, repo_name):
404 405 """
405 406 Set's this repository to be visible in public journal,
406 407 in other words assing default user to follow this repo
407 408
408 409 :param repo_name:
409 410 """
410 411
411 412 cur_token = request.POST.get('auth_token')
412 413 token = get_token()
413 414 if cur_token == token:
414 415 try:
415 416 repo_id = Repository.get_by_repo_name(repo_name).repo_id
416 417 user_id = User.get_by_username('default').user_id
417 418 self.scm_model.toggle_following_repo(repo_id, user_id)
418 419 h.flash(_('Updated repository visibility in public journal'),
419 420 category='success')
420 421 Session().commit()
421 422 except:
422 423 h.flash(_('An error occurred during setting this'
423 424 ' repository in public journal'),
424 425 category='error')
425 426
426 427 else:
427 428 h.flash(_('Token mismatch'), category='error')
428 429 return redirect(url('edit_repo', repo_name=repo_name))
429 430
430 431 @HasPermissionAllDecorator('hg.admin')
431 432 def repo_pull(self, repo_name):
432 433 """
433 434 Runs task to update given repository with remote changes,
434 435 ie. make pull on remote location
435 436
436 437 :param repo_name:
437 438 """
438 439 try:
439 440 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
440 441 h.flash(_('Pulled from remote location'), category='success')
441 442 except Exception, e:
442 443 h.flash(_('An error occurred during pull from remote location'),
443 444 category='error')
444 445
445 446 return redirect(url('edit_repo', repo_name=repo_name))
446 447
447 448 @HasPermissionAllDecorator('hg.admin')
448 449 def repo_as_fork(self, repo_name):
449 450 """
450 451 Mark given repository as a fork of another
451 452
452 453 :param repo_name:
453 454 """
454 455 try:
455 456 fork_id = request.POST.get('id_fork_of')
456 457 repo = ScmModel().mark_as_fork(repo_name, fork_id,
457 458 self.rhodecode_user.username)
458 459 fork = repo.fork.repo_name if repo.fork else _('Nothing')
459 460 Session().commit()
460 461 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
461 462 category='success')
462 463 except Exception, e:
463 464 log.error(traceback.format_exc())
464 465 h.flash(_('An error occurred during this operation'),
465 466 category='error')
466 467
467 468 return redirect(url('edit_repo', repo_name=repo_name))
468 469
469 470 @HasPermissionAllDecorator('hg.admin')
470 471 def show(self, repo_name, format='html'):
471 472 """GET /repos/repo_name: Show a specific item"""
472 473 # url('repo', repo_name=ID)
473 474
474 475 @HasPermissionAllDecorator('hg.admin')
475 476 def edit(self, repo_name, format='html'):
476 477 """GET /repos/repo_name/edit: Form to edit an existing item"""
477 478 # url('edit_repo', repo_name=ID)
478 479 defaults = self.__load_data(repo_name)
479 480
480 481 return htmlfill.render(
481 482 render('admin/repos/repo_edit.html'),
482 483 defaults=defaults,
483 484 encoding="UTF-8",
484 485 force_defaults=False
485 486 )
@@ -1,438 +1,479 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 import pkg_resources
30 30 import platform
31 31
32 32 from sqlalchemy import func
33 33 from formencode import htmlfill
34 34 from pylons import request, session, tmpl_context as c, url, config
35 35 from pylons.controllers.util import abort, redirect
36 36 from pylons.i18n.translation import _
37 37
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, NotAnonymous
41 41 from rhodecode.lib.base import BaseController, render
42 42 from rhodecode.lib.celerylib import tasks, run_task
43 43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 44 set_rhodecode_config, repo_name_slug
45 45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 46 RhodeCodeSetting, PullRequest, PullRequestReviewers
47 47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 ApplicationUiSettingsForm
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
49 49 from rhodecode.model.scm import ScmModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.db import User
52 52 from rhodecode.model.notification import EmailNotificationModel
53 53 from rhodecode.model.meta import Session
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class SettingsController(BaseController):
59 59 """REST Controller styled on the Atom Publishing Protocol"""
60 60 # To properly map this controller, ensure your config/routing.py
61 61 # file has a resource setup:
62 62 # map.resource('setting', 'settings', controller='admin/settings',
63 63 # path_prefix='/admin', name_prefix='admin_')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 67 c.admin_user = session.get('admin_user')
68 68 c.admin_username = session.get('admin_username')
69 69 c.modules = sorted([(p.project_name, p.version)
70 70 for p in pkg_resources.working_set],
71 71 key=lambda k: k[0].lower())
72 72 c.py_version = platform.python_version()
73 73 c.platform = platform.platform()
74 74 super(SettingsController, self).__before__()
75 75
76 76 @HasPermissionAllDecorator('hg.admin')
77 77 def index(self, format='html'):
78 78 """GET /admin/settings: All items in the collection"""
79 79 # url('admin_settings')
80 80
81 81 defaults = RhodeCodeSetting.get_app_settings()
82 82 defaults.update(self.get_hg_ui_settings())
83 83
84 84 return htmlfill.render(
85 85 render('admin/settings/settings.html'),
86 86 defaults=defaults,
87 87 encoding="UTF-8",
88 88 force_defaults=False
89 89 )
90 90
91 91 @HasPermissionAllDecorator('hg.admin')
92 92 def create(self):
93 93 """POST /admin/settings: Create a new item"""
94 94 # url('admin_settings')
95 95
96 96 @HasPermissionAllDecorator('hg.admin')
97 97 def new(self, format='html'):
98 98 """GET /admin/settings/new: Form to create a new item"""
99 99 # url('admin_new_setting')
100 100
101 101 @HasPermissionAllDecorator('hg.admin')
102 102 def update(self, setting_id):
103 103 """PUT /admin/settings/setting_id: Update an existing item"""
104 104 # Forms posted to this method should contain a hidden field:
105 105 # <input type="hidden" name="_method" value="PUT" />
106 106 # Or using helpers:
107 107 # h.form(url('admin_setting', setting_id=ID),
108 108 # method='put')
109 109 # url('admin_setting', setting_id=ID)
110 110
111 111 if setting_id == 'mapping':
112 112 rm_obsolete = request.POST.get('destroy', False)
113 113 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
114 114 initial = ScmModel().repo_scan()
115 115 log.debug('invalidating all repositories')
116 116 for repo_name in initial.keys():
117 117 invalidate_cache('get_repo_cached_%s' % repo_name)
118 118
119 119 added, removed = repo2db_mapper(initial, rm_obsolete)
120 120
121 121 h.flash(_('Repositories successfully'
122 122 ' rescanned added: %s,removed: %s') % (added, removed),
123 123 category='success')
124 124
125 125 if setting_id == 'whoosh':
126 126 repo_location = self.get_hg_ui_settings()['paths_root_path']
127 127 full_index = request.POST.get('full_index', False)
128 128 run_task(tasks.whoosh_index, repo_location, full_index)
129 129 h.flash(_('Whoosh reindex task scheduled'), category='success')
130 130
131 131 if setting_id == 'global':
132 132
133 133 application_form = ApplicationSettingsForm()()
134 134 try:
135 135 form_result = application_form.to_python(dict(request.POST))
136 136 except formencode.Invalid, errors:
137 137 return htmlfill.render(
138 138 render('admin/settings/settings.html'),
139 139 defaults=errors.value,
140 140 errors=errors.error_dict or {},
141 141 prefix_error=False,
142 142 encoding="UTF-8"
143 143 )
144 144
145 145 try:
146 sett1 = RhodeCodeSetting.get_by_name('title')
146 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
147 147 sett1.app_settings_value = form_result['rhodecode_title']
148 148 Session().add(sett1)
149 149
150 sett2 = RhodeCodeSetting.get_by_name('realm')
150 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
151 151 sett2.app_settings_value = form_result['rhodecode_realm']
152 152 Session().add(sett2)
153 153
154 sett3 = RhodeCodeSetting.get_by_name('ga_code')
154 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
155 155 sett3.app_settings_value = form_result['rhodecode_ga_code']
156 156 Session().add(sett3)
157 157
158 158 Session().commit()
159 159 set_rhodecode_config(config)
160 160 h.flash(_('Updated application settings'), category='success')
161 161
162 162 except Exception:
163 163 log.error(traceback.format_exc())
164 164 h.flash(_('error occurred during updating '
165 165 'application settings'),
166 166 category='error')
167 167
168 if setting_id == 'visual':
169
170 application_form = ApplicationVisualisationForm()()
171 try:
172 form_result = application_form.to_python(dict(request.POST))
173 except formencode.Invalid, errors:
174 return htmlfill.render(
175 render('admin/settings/settings.html'),
176 defaults=errors.value,
177 errors=errors.error_dict or {},
178 prefix_error=False,
179 encoding="UTF-8"
180 )
181
182 try:
183 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
184 sett1.app_settings_value = \
185 form_result['rhodecode_show_public_icon']
186
187 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
188 sett2.app_settings_value = \
189 form_result['rhodecode_show_private_icon']
190
191 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
192 sett3.app_settings_value = \
193 form_result['rhodecode_stylify_metatags']
194
195 Session().add(sett1)
196 Session().add(sett2)
197 Session().add(sett3)
198 Session().commit()
199 set_rhodecode_config(config)
200 h.flash(_('Updated visualisation settings'),
201 category='success')
202
203 except Exception:
204 log.error(traceback.format_exc())
205 h.flash(_('error occurred during updating '
206 'visualisation settings'),
207 category='error')
208
168 209 if setting_id == 'vcs':
169 210 application_form = ApplicationUiSettingsForm()()
170 211 try:
171 212 form_result = application_form.to_python(dict(request.POST))
172 213 except formencode.Invalid, errors:
173 214 return htmlfill.render(
174 215 render('admin/settings/settings.html'),
175 216 defaults=errors.value,
176 217 errors=errors.error_dict or {},
177 218 prefix_error=False,
178 219 encoding="UTF-8"
179 220 )
180 221
181 222 try:
182 223 # fix namespaces for hooks
183 224 _f = lambda s: s.replace('.', '_')
184 225
185 226 sett1 = RhodeCodeUi.query()\
186 227 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
187 228 sett1.ui_value = form_result['web_push_ssl']
188 229
189 230 sett2 = RhodeCodeUi.query()\
190 231 .filter(RhodeCodeUi.ui_key == '/').one()
191 232 sett2.ui_value = form_result['paths_root_path']
192 233
193 234 #HOOKS
194 235 sett3 = RhodeCodeUi.query()\
195 236 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_UPDATE)\
196 237 .one()
197 238 sett3.ui_active = bool(form_result[_f('hooks_%s' %
198 239 RhodeCodeUi.HOOK_UPDATE)])
199 240
200 241 sett4 = RhodeCodeUi.query()\
201 242 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_REPO_SIZE)\
202 243 .one()
203 244 sett4.ui_active = bool(form_result[_f('hooks_%s' %
204 245 RhodeCodeUi.HOOK_REPO_SIZE)])
205 246
206 247 sett5 = RhodeCodeUi.query()\
207 248 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PUSH)\
208 249 .one()
209 250 sett5.ui_active = bool(form_result[_f('hooks_%s' %
210 251 RhodeCodeUi.HOOK_PUSH)])
211 252
212 253 sett6 = RhodeCodeUi.query()\
213 254 .filter(RhodeCodeUi.ui_key == RhodeCodeUi.HOOK_PULL)\
214 255 .one()
215 256 sett6.ui_active = bool(form_result[_f('hooks_%s' %
216 257 RhodeCodeUi.HOOK_PULL)])
217 258
218 259 Session().add(sett1)
219 260 Session().add(sett2)
220 261 Session().add(sett3)
221 262 Session().add(sett4)
222 263 Session().add(sett5)
223 264 Session().add(sett6)
224 265 Session().commit()
225 266
226 267 h.flash(_('Updated mercurial settings'), category='success')
227 268
228 269 except Exception:
229 270 log.error(traceback.format_exc())
230 271 h.flash(_('error occurred during updating '
231 272 'application settings'), category='error')
232 273
233 274 if setting_id == 'hooks':
234 275 ui_key = request.POST.get('new_hook_ui_key')
235 276 ui_value = request.POST.get('new_hook_ui_value')
236 277 try:
237 278
238 279 if ui_value and ui_key:
239 280 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
240 281 h.flash(_('Added new hook'),
241 282 category='success')
242 283
243 284 # check for edits
244 285 update = False
245 286 _d = request.POST.dict_of_lists()
246 287 for k, v in zip(_d.get('hook_ui_key', []),
247 288 _d.get('hook_ui_value_new', [])):
248 289 RhodeCodeUi.create_or_update_hook(k, v)
249 290 update = True
250 291
251 292 if update:
252 293 h.flash(_('Updated hooks'), category='success')
253 294 Session().commit()
254 295 except Exception:
255 296 log.error(traceback.format_exc())
256 297 h.flash(_('error occurred during hook creation'),
257 298 category='error')
258 299
259 300 return redirect(url('admin_edit_setting', setting_id='hooks'))
260 301
261 302 if setting_id == 'email':
262 303 test_email = request.POST.get('test_email')
263 304 test_email_subj = 'RhodeCode TestEmail'
264 305 test_email_body = 'RhodeCode Email test'
265 306
266 307 test_email_html_body = EmailNotificationModel()\
267 308 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
268 309 body=test_email_body)
269 310
270 311 recipients = [test_email] if [test_email] else None
271 312
272 313 run_task(tasks.send_email, recipients, test_email_subj,
273 314 test_email_body, test_email_html_body)
274 315
275 316 h.flash(_('Email task created'), category='success')
276 317 return redirect(url('admin_settings'))
277 318
278 319 @HasPermissionAllDecorator('hg.admin')
279 320 def delete(self, setting_id):
280 321 """DELETE /admin/settings/setting_id: Delete an existing item"""
281 322 # Forms posted to this method should contain a hidden field:
282 323 # <input type="hidden" name="_method" value="DELETE" />
283 324 # Or using helpers:
284 325 # h.form(url('admin_setting', setting_id=ID),
285 326 # method='delete')
286 327 # url('admin_setting', setting_id=ID)
287 328 if setting_id == 'hooks':
288 329 hook_id = request.POST.get('hook_id')
289 330 RhodeCodeUi.delete(hook_id)
290 331 Session().commit()
291 332
292 333 @HasPermissionAllDecorator('hg.admin')
293 334 def show(self, setting_id, format='html'):
294 335 """
295 336 GET /admin/settings/setting_id: Show a specific item"""
296 337 # url('admin_setting', setting_id=ID)
297 338
298 339 @HasPermissionAllDecorator('hg.admin')
299 340 def edit(self, setting_id, format='html'):
300 341 """
301 342 GET /admin/settings/setting_id/edit: Form to
302 343 edit an existing item"""
303 344 # url('admin_edit_setting', setting_id=ID)
304 345
305 346 c.hooks = RhodeCodeUi.get_builtin_hooks()
306 347 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
307 348
308 349 return htmlfill.render(
309 350 render('admin/settings/hooks.html'),
310 351 defaults={},
311 352 encoding="UTF-8",
312 353 force_defaults=False
313 354 )
314 355
315 356 @NotAnonymous()
316 357 def my_account(self):
317 358 """
318 359 GET /_admin/my_account Displays info about my account
319 360 """
320 361 # url('admin_settings_my_account')
321 362
322 363 c.user = User.get(self.rhodecode_user.user_id)
323 364 all_repos = Session().query(Repository)\
324 365 .filter(Repository.user_id == c.user.user_id)\
325 366 .order_by(func.lower(Repository.repo_name)).all()
326 367
327 368 c.user_repos = ScmModel().get_repos(all_repos)
328 369
329 370 if c.user.username == 'default':
330 371 h.flash(_("You can't edit this user since it's"
331 372 " crucial for entire application"), category='warning')
332 373 return redirect(url('users'))
333 374
334 375 defaults = c.user.get_dict()
335 376
336 377 c.form = htmlfill.render(
337 378 render('admin/users/user_edit_my_account_form.html'),
338 379 defaults=defaults,
339 380 encoding="UTF-8",
340 381 force_defaults=False
341 382 )
342 383 return render('admin/users/user_edit_my_account.html')
343 384
344 385 @NotAnonymous()
345 386 def my_account_update(self):
346 387 """PUT /_admin/my_account_update: Update an existing item"""
347 388 # Forms posted to this method should contain a hidden field:
348 389 # <input type="hidden" name="_method" value="PUT" />
349 390 # Or using helpers:
350 391 # h.form(url('admin_settings_my_account_update'),
351 392 # method='put')
352 393 # url('admin_settings_my_account_update', id=ID)
353 394 uid = self.rhodecode_user.user_id
354 395 email = self.rhodecode_user.email
355 396 _form = UserForm(edit=True,
356 397 old_data={'user_id': uid, 'email': email})()
357 398 form_result = {}
358 399 try:
359 400 form_result = _form.to_python(dict(request.POST))
360 401 UserModel().update_my_account(uid, form_result)
361 402 h.flash(_('Your account was updated successfully'),
362 403 category='success')
363 404 Session().commit()
364 405 except formencode.Invalid, errors:
365 406 c.user = User.get(self.rhodecode_user.user_id)
366 407
367 408 c.form = htmlfill.render(
368 409 render('admin/users/user_edit_my_account_form.html'),
369 410 defaults=errors.value,
370 411 errors=errors.error_dict or {},
371 412 prefix_error=False,
372 413 encoding="UTF-8")
373 414 return render('admin/users/user_edit_my_account.html')
374 415 except Exception:
375 416 log.error(traceback.format_exc())
376 417 h.flash(_('error occurred during update of user %s') \
377 418 % form_result.get('username'), category='error')
378 419
379 420 return redirect(url('my_account'))
380 421
381 422 @NotAnonymous()
382 423 def my_account_my_repos(self):
383 424 all_repos = Session().query(Repository)\
384 425 .filter(Repository.user_id == self.rhodecode_user.user_id)\
385 426 .order_by(func.lower(Repository.repo_name))\
386 427 .all()
387 428 c.user_repos = ScmModel().get_repos(all_repos)
388 429 return render('admin/users/user_edit_my_account_repos.html')
389 430
390 431 @NotAnonymous()
391 432 def my_account_my_pullrequests(self):
392 433 c.my_pull_requests = PullRequest.query()\
393 434 .filter(PullRequest.user_id==
394 435 self.rhodecode_user.user_id)\
395 436 .all()
396 437 c.participate_in_pull_requests = \
397 438 [x.pull_request for x in PullRequestReviewers.query()\
398 439 .filter(PullRequestReviewers.user_id==
399 440 self.rhodecode_user.user_id)\
400 441 .all()]
401 442 return render('admin/users/user_edit_my_account_pullrequests.html')
402 443
403 444 @NotAnonymous()
404 445 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
405 446 def create_repository(self):
406 447 """GET /_admin/create_repository: Form to create a new item"""
407 448
408 449 c.repo_groups = RepoGroup.groups_choices()
409 450 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
410 451 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
411 452
412 453 new_repo = request.GET.get('repo', '')
413 454 c.new_repo = repo_name_slug(new_repo)
414 455
415 456 return render('admin/repos/repo_add_create_repository.html')
416 457
417 458 @NotAnonymous()
418 459 def get_hg_ui_settings(self):
419 460 ret = RhodeCodeUi.query().all()
420 461
421 462 if not ret:
422 463 raise Exception('Could not get application ui settings !')
423 464 settings = {}
424 465 for each in ret:
425 466 k = each.ui_key
426 467 v = each.ui_value
427 468 if k == '/':
428 469 k = 'root_path'
429 470
430 471 if k.find('.') != -1:
431 472 k = k.replace('.', '_')
432 473
433 474 if each.ui_section == 'hooks':
434 475 v = each.ui_active
435 476
436 477 settings[each.ui_section + '_' + k] = v
437 478
438 479 return settings
@@ -1,295 +1,295 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Users crud controller for pylons
7 7
8 8 :created_on: Apr 4, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from pylons import response
30 30
31 31 from formencode import htmlfill
32 32 from pylons import request, session, tmpl_context as c, url, config
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib.exceptions import DefaultUserException, \
37 37 UserOwnsReposException
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 AuthUser
41 41 from rhodecode.lib.base import BaseController, render
42 42
43 43 import rhodecode
44 44 from rhodecode.model.db import User, Permission, UserEmailMap
45 45 from rhodecode.model.forms import UserForm
46 46 from rhodecode.model.user import UserModel
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.lib.utils import action_logger
49 49 from rhodecode.lib.compat import json
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class UsersController(BaseController):
55 55 """REST Controller styled on the Atom Publishing Protocol"""
56 56 # To properly map this controller, ensure your config/routing.py
57 57 # file has a resource setup:
58 58 # map.resource('user', 'users')
59 59
60 60 @LoginRequired()
61 61 @HasPermissionAllDecorator('hg.admin')
62 62 def __before__(self):
63 63 c.admin_user = session.get('admin_user')
64 64 c.admin_username = session.get('admin_username')
65 65 super(UsersController, self).__before__()
66 66 c.available_permissions = config['available_permissions']
67 67
68 68 def index(self, format='html'):
69 69 """GET /users: All items in the collection"""
70 70 # url('users')
71 71
72 72 c.users_list = User.query().order_by(User.username).all()
73 73
74 74 users_data = []
75 75 total_records = len(c.users_list)
76 76 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
77 77 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
78 78
79 79 grav_tmpl = lambda user_email, size: (
80 80 template.get_def("user_gravatar")
81 .render(user_email, size, _=_, h=h))
81 .render(user_email, size, _=_, h=h, c=c))
82 82
83 83 user_lnk = lambda user_id, username: (
84 84 template.get_def("user_name")
85 .render(user_id, username, _=_, h=h))
85 .render(user_id, username, _=_, h=h, c=c))
86 86
87 87 user_actions = lambda user_id, username: (
88 88 template.get_def("user_actions")
89 .render(user_id, username, _=_, h=h))
89 .render(user_id, username, _=_, h=h, c=c))
90 90
91 91 for user in c.users_list:
92 92 users_data.append({
93 93 "gravatar": grav_tmpl(user. email, 24),
94 94 "raw_username": user.username,
95 95 "username": user_lnk(user.user_id, user.username),
96 96 "firstname": user.name,
97 97 "lastname": user.lastname,
98 98 "last_login": h.fmt_date(user.last_login),
99 99 "active": h.bool2icon(user.active),
100 100 "admin": h.bool2icon(user.admin),
101 101 "ldap": h.bool2icon(bool(user.ldap_dn)),
102 102 "action": user_actions(user.user_id, user.username),
103 103 })
104 104
105 105 c.data = json.dumps({
106 106 "totalRecords": total_records,
107 107 "startIndex": 0,
108 108 "sort": None,
109 109 "dir": "asc",
110 110 "records": users_data
111 111 })
112 112
113 113 return render('admin/users/users.html')
114 114
115 115 def create(self):
116 116 """POST /users: Create a new item"""
117 117 # url('users')
118 118
119 119 user_model = UserModel()
120 120 user_form = UserForm()()
121 121 try:
122 122 form_result = user_form.to_python(dict(request.POST))
123 123 user_model.create(form_result)
124 124 usr = form_result['username']
125 125 action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr,
126 126 None, self.ip_addr, self.sa)
127 127 h.flash(_('created user %s') % usr,
128 128 category='success')
129 129 Session().commit()
130 130 except formencode.Invalid, errors:
131 131 return htmlfill.render(
132 132 render('admin/users/user_add.html'),
133 133 defaults=errors.value,
134 134 errors=errors.error_dict or {},
135 135 prefix_error=False,
136 136 encoding="UTF-8")
137 137 except Exception:
138 138 log.error(traceback.format_exc())
139 139 h.flash(_('error occurred during creation of user %s') \
140 140 % request.POST.get('username'), category='error')
141 141 return redirect(url('users'))
142 142
143 143 def new(self, format='html'):
144 144 """GET /users/new: Form to create a new item"""
145 145 # url('new_user')
146 146 return render('admin/users/user_add.html')
147 147
148 148 def update(self, id):
149 149 """PUT /users/id: Update an existing item"""
150 150 # Forms posted to this method should contain a hidden field:
151 151 # <input type="hidden" name="_method" value="PUT" />
152 152 # Or using helpers:
153 153 # h.form(url('update_user', id=ID),
154 154 # method='put')
155 155 # url('user', id=ID)
156 156 user_model = UserModel()
157 157 c.user = user_model.get(id)
158 158 c.perm_user = AuthUser(user_id=id)
159 159 _form = UserForm(edit=True, old_data={'user_id': id,
160 160 'email': c.user.email})()
161 161 form_result = {}
162 162 try:
163 163 form_result = _form.to_python(dict(request.POST))
164 164 user_model.update(id, form_result)
165 165 usr = form_result['username']
166 166 action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
167 167 None, self.ip_addr, self.sa)
168 168 h.flash(_('User updated successfully'), category='success')
169 169 Session().commit()
170 170 except formencode.Invalid, errors:
171 171 c.user_email_map = UserEmailMap.query()\
172 172 .filter(UserEmailMap.user == c.user).all()
173 173 defaults = errors.value
174 174 e = errors.error_dict or {}
175 175 perm = Permission.get_by_key('hg.create.repository')
176 176 defaults.update({'create_repo_perm': user_model.has_perm(id, perm)})
177 177 defaults.update({'_method': 'put'})
178 178 return htmlfill.render(
179 179 render('admin/users/user_edit.html'),
180 180 defaults=defaults,
181 181 errors=e,
182 182 prefix_error=False,
183 183 encoding="UTF-8")
184 184 except Exception:
185 185 log.error(traceback.format_exc())
186 186 h.flash(_('error occurred during update of user %s') \
187 187 % form_result.get('username'), category='error')
188 188 return redirect(url('users'))
189 189
190 190 def delete(self, id):
191 191 """DELETE /users/id: Delete an existing item"""
192 192 # Forms posted to this method should contain a hidden field:
193 193 # <input type="hidden" name="_method" value="DELETE" />
194 194 # Or using helpers:
195 195 # h.form(url('delete_user', id=ID),
196 196 # method='delete')
197 197 # url('user', id=ID)
198 198 user_model = UserModel()
199 199 try:
200 200 user_model.delete(id)
201 201 Session().commit()
202 202 h.flash(_('successfully deleted user'), category='success')
203 203 except (UserOwnsReposException, DefaultUserException), e:
204 204 h.flash(e, category='warning')
205 205 except Exception:
206 206 log.error(traceback.format_exc())
207 207 h.flash(_('An error occurred during deletion of user'),
208 208 category='error')
209 209 return redirect(url('users'))
210 210
211 211 def show(self, id, format='html'):
212 212 """GET /users/id: Show a specific item"""
213 213 # url('user', id=ID)
214 214
215 215 def edit(self, id, format='html'):
216 216 """GET /users/id/edit: Form to edit an existing item"""
217 217 # url('edit_user', id=ID)
218 218 c.user = User.get_or_404(id)
219 219
220 220 if c.user.username == 'default':
221 221 h.flash(_("You can't edit this user"), category='warning')
222 222 return redirect(url('users'))
223 223 c.perm_user = AuthUser(user_id=id)
224 224 c.user.permissions = {}
225 225 c.granted_permissions = UserModel().fill_perms(c.user)\
226 226 .permissions['global']
227 227 c.user_email_map = UserEmailMap.query()\
228 228 .filter(UserEmailMap.user == c.user).all()
229 229 defaults = c.user.get_dict()
230 230 perm = Permission.get_by_key('hg.create.repository')
231 231 defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)})
232 232
233 233 return htmlfill.render(
234 234 render('admin/users/user_edit.html'),
235 235 defaults=defaults,
236 236 encoding="UTF-8",
237 237 force_defaults=False
238 238 )
239 239
240 240 def update_perm(self, id):
241 241 """PUT /users_perm/id: Update an existing item"""
242 242 # url('user_perm', id=ID, method='put')
243 243
244 244 grant_perm = request.POST.get('create_repo_perm', False)
245 245 user_model = UserModel()
246 246
247 247 if grant_perm:
248 248 perm = Permission.get_by_key('hg.create.none')
249 249 user_model.revoke_perm(id, perm)
250 250
251 251 perm = Permission.get_by_key('hg.create.repository')
252 252 user_model.grant_perm(id, perm)
253 253 h.flash(_("Granted 'repository create' permission to user"),
254 254 category='success')
255 255 Session().commit()
256 256 else:
257 257 perm = Permission.get_by_key('hg.create.repository')
258 258 user_model.revoke_perm(id, perm)
259 259
260 260 perm = Permission.get_by_key('hg.create.none')
261 261 user_model.grant_perm(id, perm)
262 262 h.flash(_("Revoked 'repository create' permission to user"),
263 263 category='success')
264 264 Session().commit()
265 265 return redirect(url('edit_user', id=id))
266 266
267 267 def add_email(self, id):
268 268 """POST /user_emails:Add an existing item"""
269 269 # url('user_emails', id=ID, method='put')
270 270
271 271 #TODO: validation and form !!!
272 272 email = request.POST.get('new_email')
273 273 user_model = UserModel()
274 274
275 275 try:
276 276 user_model.add_extra_email(id, email)
277 277 Session().commit()
278 278 h.flash(_("Added email %s to user") % email, category='success')
279 279 except formencode.Invalid, error:
280 280 msg = error.error_dict['email']
281 281 h.flash(msg, category='error')
282 282 except Exception:
283 283 log.error(traceback.format_exc())
284 284 h.flash(_('An error occurred during email saving'),
285 285 category='error')
286 286 return redirect(url('edit_user', id=id))
287 287
288 288 def delete_email(self, id):
289 289 """DELETE /user_emails_delete/id: Delete an existing item"""
290 290 # url('user_emails_delete', id=ID, method='delete')
291 291 user_model = UserModel()
292 292 user_model.delete_extra_email(id, request.POST.get('del_email'))
293 293 Session().commit()
294 294 h.flash(_("Removed email from user"), category='success')
295 295 return redirect(url('edit_user', id=id))
@@ -1,251 +1,257 b''
1 1 """The base Controller API
2 2
3 3 Provides the BaseController class for subclassing.
4 4 """
5 5 import logging
6 6 import time
7 7 import traceback
8 8
9 9 from paste.auth.basic import AuthBasicAuthenticator
10 10 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
11 11 from paste.httpheaders import WWW_AUTHENTICATE
12 12
13 13 from pylons import config, tmpl_context as c, request, session, url
14 14 from pylons.controllers import WSGIController
15 15 from pylons.controllers.util import redirect
16 16 from pylons.templating import render_mako as render
17 17
18 18 from rhodecode import __version__, BACKENDS
19 19
20 from rhodecode.lib.utils2 import str2bool, safe_unicode
20 from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
21 21 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
22 22 HasPermissionAnyMiddleware, CookieStoreWrapper
23 23 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
24 24 from rhodecode.model import meta
25 25
26 26 from rhodecode.model.db import Repository, RhodeCodeUi
27 27 from rhodecode.model.notification import NotificationModel
28 28 from rhodecode.model.scm import ScmModel
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 def _get_ip_addr(environ):
34 34 proxy_key = 'HTTP_X_REAL_IP'
35 35 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
36 36 def_key = 'REMOTE_ADDR'
37 37
38 38 ip = environ.get(proxy_key2)
39 39 if ip:
40 40 return ip
41 41
42 42 ip = environ.get(proxy_key)
43 43
44 44 if ip:
45 45 return ip
46 46
47 47 ip = environ.get(def_key, '0.0.0.0')
48 48 return ip
49 49
50 50
51 51 def _get_access_path(environ):
52 52 path = environ.get('PATH_INFO')
53 53 org_req = environ.get('pylons.original_request')
54 54 if org_req:
55 55 path = org_req.environ.get('PATH_INFO')
56 56 return path
57 57
58 58
59 59 class BasicAuth(AuthBasicAuthenticator):
60 60
61 61 def __init__(self, realm, authfunc, auth_http_code=None):
62 62 self.realm = realm
63 63 self.authfunc = authfunc
64 64 self._rc_auth_http_code = auth_http_code
65 65
66 66 def build_authentication(self):
67 67 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
68 68 if self._rc_auth_http_code and self._rc_auth_http_code == '403':
69 69 # return 403 if alternative http return code is specified in
70 70 # RhodeCode config
71 71 return HTTPForbidden(headers=head)
72 72 return HTTPUnauthorized(headers=head)
73 73
74 74
75 75 class BaseVCSController(object):
76 76
77 77 def __init__(self, application, config):
78 78 self.application = application
79 79 self.config = config
80 80 # base path of repo locations
81 81 self.basepath = self.config['base_path']
82 82 #authenticate this mercurial request using authfunc
83 83 self.authenticate = BasicAuth('', authfunc,
84 84 config.get('auth_ret_code'))
85 85 self.ipaddr = '0.0.0.0'
86 86
87 87 def _handle_request(self, environ, start_response):
88 88 raise NotImplementedError()
89 89
90 90 def _get_by_id(self, repo_name):
91 91 """
92 92 Get's a special pattern _<ID> from clone url and tries to replace it
93 93 with a repository_name for support of _<ID> non changable urls
94 94
95 95 :param repo_name:
96 96 """
97 97 try:
98 98 data = repo_name.split('/')
99 99 if len(data) >= 2:
100 100 by_id = data[1].split('_')
101 101 if len(by_id) == 2 and by_id[1].isdigit():
102 102 _repo_name = Repository.get(by_id[1]).repo_name
103 103 data[1] = _repo_name
104 104 except:
105 105 log.debug('Failed to extract repo_name from id %s' % (
106 106 traceback.format_exc()
107 107 )
108 108 )
109 109
110 110 return '/'.join(data)
111 111
112 112 def _invalidate_cache(self, repo_name):
113 113 """
114 114 Set's cache for this repository for invalidation on next access
115 115
116 116 :param repo_name: full repo name, also a cache key
117 117 """
118 118 invalidate_cache('get_repo_cached_%s' % repo_name)
119 119
120 120 def _check_permission(self, action, user, repo_name):
121 121 """
122 122 Checks permissions using action (push/pull) user and repository
123 123 name
124 124
125 125 :param action: push or pull action
126 126 :param user: user instance
127 127 :param repo_name: repository name
128 128 """
129 129 if action == 'push':
130 130 if not HasPermissionAnyMiddleware('repository.write',
131 131 'repository.admin')(user,
132 132 repo_name):
133 133 return False
134 134
135 135 else:
136 136 #any other action need at least read permission
137 137 if not HasPermissionAnyMiddleware('repository.read',
138 138 'repository.write',
139 139 'repository.admin')(user,
140 140 repo_name):
141 141 return False
142 142
143 143 return True
144 144
145 145 def _get_ip_addr(self, environ):
146 146 return _get_ip_addr(environ)
147 147
148 148 def _check_ssl(self, environ, start_response):
149 149 """
150 150 Checks the SSL check flag and returns False if SSL is not present
151 151 and required True otherwise
152 152 """
153 153 org_proto = environ['wsgi._org_proto']
154 154 #check if we have SSL required ! if not it's a bad request !
155 155 require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl')\
156 156 .scalar().ui_value)
157 157 if require_ssl and org_proto == 'http':
158 158 log.debug('proto is %s and SSL is required BAD REQUEST !'
159 159 % org_proto)
160 160 return False
161 161 return True
162 162
163 163 def __call__(self, environ, start_response):
164 164 start = time.time()
165 165 try:
166 166 return self._handle_request(environ, start_response)
167 167 finally:
168 168 log = logging.getLogger('rhodecode.' + self.__class__.__name__)
169 169 log.debug('Request time: %.3fs' % (time.time() - start))
170 170 meta.Session.remove()
171 171
172 172
173 173 class BaseController(WSGIController):
174 174
175 175 def __before__(self):
176 176 c.rhodecode_version = __version__
177 177 c.rhodecode_instanceid = config.get('instance_id')
178 178 c.rhodecode_name = config.get('rhodecode_title')
179 179 c.use_gravatar = str2bool(config.get('use_gravatar'))
180 180 c.ga_code = config.get('rhodecode_ga_code')
181 # Visual options
182 c.visual = AttributeDict({})
183 c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
184 c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
185 c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
186
181 187 c.repo_name = get_repo_slug(request)
182 188 c.backends = BACKENDS.keys()
183 189 c.unread_notifications = NotificationModel()\
184 190 .get_unread_cnt_for_user(c.rhodecode_user.user_id)
185 191 self.cut_off_limit = int(config.get('cut_off_limit'))
186 192
187 193 self.sa = meta.Session
188 194 self.scm_model = ScmModel(self.sa)
189 195 self.ip_addr = ''
190 196
191 197 def __call__(self, environ, start_response):
192 198 """Invoke the Controller"""
193 199 # WSGIController.__call__ dispatches to the Controller method
194 200 # the request is routed to. This routing information is
195 201 # available in environ['pylons.routes_dict']
196 202 start = time.time()
197 203 try:
198 204 self.ip_addr = _get_ip_addr(environ)
199 205 # make sure that we update permissions each time we call controller
200 206 api_key = request.GET.get('api_key')
201 207 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
202 208 user_id = cookie_store.get('user_id', None)
203 209 username = get_container_username(environ, config)
204 210 auth_user = AuthUser(user_id, api_key, username)
205 211 request.user = auth_user
206 212 self.rhodecode_user = c.rhodecode_user = auth_user
207 213 if not self.rhodecode_user.is_authenticated and \
208 214 self.rhodecode_user.user_id is not None:
209 215 self.rhodecode_user.set_authenticated(
210 216 cookie_store.get('is_authenticated')
211 217 )
212 218 log.info('IP: %s User: %s accessed %s' % (
213 219 self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
214 220 )
215 221 return WSGIController.__call__(self, environ, start_response)
216 222 finally:
217 223 log.info('IP: %s Request to %s time: %.3fs' % (
218 224 _get_ip_addr(environ),
219 225 safe_unicode(_get_access_path(environ)), time.time() - start)
220 226 )
221 227 meta.Session.remove()
222 228
223 229
224 230 class BaseRepoController(BaseController):
225 231 """
226 232 Base class for controllers responsible for loading all needed data for
227 233 repository loaded items are
228 234
229 235 c.rhodecode_repo: instance of scm repository
230 236 c.rhodecode_db_repo: instance of db
231 237 c.repository_followers: number of followers
232 238 c.repository_forks: number of forks
233 239 """
234 240
235 241 def __before__(self):
236 242 super(BaseRepoController, self).__before__()
237 243 if c.repo_name:
238 244
239 245 dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
240 246 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
241 247
242 248 if c.rhodecode_repo is None:
243 249 log.error('%s this repository is present in database but it '
244 250 'cannot be created as an scm instance', c.repo_name)
245 251
246 252 redirect(url('home'))
247 253
248 254 # some globals counter for menu
249 255 c.repository_followers = self.scm_model.get_followers(dbr)
250 256 c.repository_forks = self.scm_model.get_forks(dbr)
251 257 c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
@@ -1,1022 +1,1043 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 import re
12 13
13 14 from datetime import datetime
14 15 from pygments.formatters.html import HtmlFormatter
15 16 from pygments import highlight as code_highlight
16 17 from pylons import url, request, config
17 18 from pylons.i18n.translation import _, ungettext
18 19 from hashlib import md5
19 20
20 21 from webhelpers.html import literal, HTML, escape
21 22 from webhelpers.html.tools import *
22 23 from webhelpers.html.builder import make_tag
23 24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 25 end_form, file, form, hidden, image, javascript_link, link_to, \
25 26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 27 submit, text, password, textarea, title, ul, xml_declaration, radio
27 28 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 30 from webhelpers.number import format_byte_size, format_bit_size
30 31 from webhelpers.pylonslib import Flash as _Flash
31 32 from webhelpers.pylonslib.secure_form import secure_form
32 33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 35 replace_whitespace, urlify, truncate, wrap_paragraphs
35 36 from webhelpers.date import time_ago_in_words
36 37 from webhelpers.paginate import Page
37 38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 39 convert_boolean_attrs, NotGiven, _make_safe_id_component
39 40
40 41 from rhodecode.lib.annotate import annotate_highlight
41 42 from rhodecode.lib.utils import repo_name_slug
42 43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 44 get_changeset_safe
44 45 from rhodecode.lib.markup_renderer import MarkupRenderer
45 46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
46 47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
48 49 from rhodecode.model.changeset_status import ChangesetStatusModel
49 50 from rhodecode.model.db import URL_SEP, Permission
50 51
51 52 log = logging.getLogger(__name__)
52 53
53 54
54 55 html_escape_table = {
55 56 "&": "&amp;",
56 57 '"': "&quot;",
57 58 "'": "&apos;",
58 59 ">": "&gt;",
59 60 "<": "&lt;",
60 61 }
61 62
62 63
63 64 def html_escape(text):
64 65 """Produce entities within text."""
65 66 return "".join(html_escape_table.get(c,c) for c in text)
66 67
67 68
68 69 def shorter(text, size=20):
69 70 postfix = '...'
70 71 if len(text) > size:
71 72 return text[:size - len(postfix)] + postfix
72 73 return text
73 74
74 75
75 76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
76 77 """
77 78 Reset button
78 79 """
79 80 _set_input_attrs(attrs, type, name, value)
80 81 _set_id_attr(attrs, id, name)
81 82 convert_boolean_attrs(attrs, ["disabled"])
82 83 return HTML.input(**attrs)
83 84
84 85 reset = _reset
85 86 safeid = _make_safe_id_component
86 87
87 88
88 89 def FID(raw_id, path):
89 90 """
90 91 Creates a uniqe ID for filenode based on it's hash of path and revision
91 92 it's safe to use in urls
92 93
93 94 :param raw_id:
94 95 :param path:
95 96 """
96 97
97 98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
98 99
99 100
100 101 def get_token():
101 102 """Return the current authentication token, creating one if one doesn't
102 103 already exist.
103 104 """
104 105 token_key = "_authentication_token"
105 106 from pylons import session
106 107 if not token_key in session:
107 108 try:
108 109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
109 110 except AttributeError: # Python < 2.4
110 111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
111 112 session[token_key] = token
112 113 if hasattr(session, 'save'):
113 114 session.save()
114 115 return session[token_key]
115 116
116 117
117 118 class _GetError(object):
118 119 """Get error from form_errors, and represent it as span wrapped error
119 120 message
120 121
121 122 :param field_name: field to fetch errors for
122 123 :param form_errors: form errors dict
123 124 """
124 125
125 126 def __call__(self, field_name, form_errors):
126 127 tmpl = """<span class="error_msg">%s</span>"""
127 128 if form_errors and field_name in form_errors:
128 129 return literal(tmpl % form_errors.get(field_name))
129 130
130 131 get_error = _GetError()
131 132
132 133
133 134 class _ToolTip(object):
134 135
135 136 def __call__(self, tooltip_title, trim_at=50):
136 137 """
137 138 Special function just to wrap our text into nice formatted
138 139 autowrapped text
139 140
140 141 :param tooltip_title:
141 142 """
142 143 tooltip_title = escape(tooltip_title)
143 144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
144 145 return tooltip_title
145 146 tooltip = _ToolTip()
146 147
147 148
148 149 class _FilesBreadCrumbs(object):
149 150
150 151 def __call__(self, repo_name, rev, paths):
151 152 if isinstance(paths, str):
152 153 paths = safe_unicode(paths)
153 154 url_l = [link_to(repo_name, url('files_home',
154 155 repo_name=repo_name,
155 156 revision=rev, f_path=''))]
156 157 paths_l = paths.split('/')
157 158 for cnt, p in enumerate(paths_l):
158 159 if p != '':
159 160 url_l.append(link_to(p,
160 161 url('files_home',
161 162 repo_name=repo_name,
162 163 revision=rev,
163 164 f_path='/'.join(paths_l[:cnt + 1])
164 165 )
165 166 )
166 167 )
167 168
168 169 return literal('/'.join(url_l))
169 170
170 171 files_breadcrumbs = _FilesBreadCrumbs()
171 172
172 173
173 174 class CodeHtmlFormatter(HtmlFormatter):
174 175 """
175 176 My code Html Formatter for source codes
176 177 """
177 178
178 179 def wrap(self, source, outfile):
179 180 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
180 181
181 182 def _wrap_code(self, source):
182 183 for cnt, it in enumerate(source):
183 184 i, t = it
184 185 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
185 186 yield i, t
186 187
187 188 def _wrap_tablelinenos(self, inner):
188 189 dummyoutfile = StringIO.StringIO()
189 190 lncount = 0
190 191 for t, line in inner:
191 192 if t:
192 193 lncount += 1
193 194 dummyoutfile.write(line)
194 195
195 196 fl = self.linenostart
196 197 mw = len(str(lncount + fl - 1))
197 198 sp = self.linenospecial
198 199 st = self.linenostep
199 200 la = self.lineanchors
200 201 aln = self.anchorlinenos
201 202 nocls = self.noclasses
202 203 if sp:
203 204 lines = []
204 205
205 206 for i in range(fl, fl + lncount):
206 207 if i % st == 0:
207 208 if i % sp == 0:
208 209 if aln:
209 210 lines.append('<a href="#%s%d" class="special">%*d</a>' %
210 211 (la, i, mw, i))
211 212 else:
212 213 lines.append('<span class="special">%*d</span>' % (mw, i))
213 214 else:
214 215 if aln:
215 216 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
216 217 else:
217 218 lines.append('%*d' % (mw, i))
218 219 else:
219 220 lines.append('')
220 221 ls = '\n'.join(lines)
221 222 else:
222 223 lines = []
223 224 for i in range(fl, fl + lncount):
224 225 if i % st == 0:
225 226 if aln:
226 227 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
227 228 else:
228 229 lines.append('%*d' % (mw, i))
229 230 else:
230 231 lines.append('')
231 232 ls = '\n'.join(lines)
232 233
233 234 # in case you wonder about the seemingly redundant <div> here: since the
234 235 # content in the other cell also is wrapped in a div, some browsers in
235 236 # some configurations seem to mess up the formatting...
236 237 if nocls:
237 238 yield 0, ('<table class="%stable">' % self.cssclass +
238 239 '<tr><td><div class="linenodiv" '
239 240 'style="background-color: #f0f0f0; padding-right: 10px">'
240 241 '<pre style="line-height: 125%">' +
241 242 ls + '</pre></div></td><td id="hlcode" class="code">')
242 243 else:
243 244 yield 0, ('<table class="%stable">' % self.cssclass +
244 245 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
245 246 ls + '</pre></div></td><td id="hlcode" class="code">')
246 247 yield 0, dummyoutfile.getvalue()
247 248 yield 0, '</td></tr></table>'
248 249
249 250
250 251 def pygmentize(filenode, **kwargs):
251 252 """pygmentize function using pygments
252 253
253 254 :param filenode:
254 255 """
255 256
256 257 return literal(code_highlight(filenode.content,
257 258 filenode.lexer, CodeHtmlFormatter(**kwargs)))
258 259
259 260
260 261 def pygmentize_annotation(repo_name, filenode, **kwargs):
261 262 """
262 263 pygmentize function for annotation
263 264
264 265 :param filenode:
265 266 """
266 267
267 268 color_dict = {}
268 269
269 270 def gen_color(n=10000):
270 271 """generator for getting n of evenly distributed colors using
271 272 hsv color and golden ratio. It always return same order of colors
272 273
273 274 :returns: RGB tuple
274 275 """
275 276
276 277 def hsv_to_rgb(h, s, v):
277 278 if s == 0.0:
278 279 return v, v, v
279 280 i = int(h * 6.0) # XXX assume int() truncates!
280 281 f = (h * 6.0) - i
281 282 p = v * (1.0 - s)
282 283 q = v * (1.0 - s * f)
283 284 t = v * (1.0 - s * (1.0 - f))
284 285 i = i % 6
285 286 if i == 0:
286 287 return v, t, p
287 288 if i == 1:
288 289 return q, v, p
289 290 if i == 2:
290 291 return p, v, t
291 292 if i == 3:
292 293 return p, q, v
293 294 if i == 4:
294 295 return t, p, v
295 296 if i == 5:
296 297 return v, p, q
297 298
298 299 golden_ratio = 0.618033988749895
299 300 h = 0.22717784590367374
300 301
301 302 for _ in xrange(n):
302 303 h += golden_ratio
303 304 h %= 1
304 305 HSV_tuple = [h, 0.95, 0.95]
305 306 RGB_tuple = hsv_to_rgb(*HSV_tuple)
306 307 yield map(lambda x: str(int(x * 256)), RGB_tuple)
307 308
308 309 cgenerator = gen_color()
309 310
310 311 def get_color_string(cs):
311 312 if cs in color_dict:
312 313 col = color_dict[cs]
313 314 else:
314 315 col = color_dict[cs] = cgenerator.next()
315 316 return "color: rgb(%s)! important;" % (', '.join(col))
316 317
317 318 def url_func(repo_name):
318 319
319 320 def _url_func(changeset):
320 321 author = changeset.author
321 322 date = changeset.date
322 323 message = tooltip(changeset.message)
323 324
324 325 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
325 326 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
326 327 "</b> %s<br/></div>")
327 328
328 329 tooltip_html = tooltip_html % (author, date, message)
329 330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
330 331 short_id(changeset.raw_id))
331 332 uri = link_to(
332 333 lnk_format,
333 334 url('changeset_home', repo_name=repo_name,
334 335 revision=changeset.raw_id),
335 336 style=get_color_string(changeset.raw_id),
336 337 class_='tooltip',
337 338 title=tooltip_html
338 339 )
339 340
340 341 uri += '\n'
341 342 return uri
342 343 return _url_func
343 344
344 345 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
345 346
346 347
347 348 def is_following_repo(repo_name, user_id):
348 349 from rhodecode.model.scm import ScmModel
349 350 return ScmModel().is_following_repo(repo_name, user_id)
350 351
351 352 flash = _Flash()
352 353
353 354 #==============================================================================
354 355 # SCM FILTERS available via h.
355 356 #==============================================================================
356 357 from rhodecode.lib.vcs.utils import author_name, author_email
357 358 from rhodecode.lib.utils2 import credentials_filter, age as _age
358 359 from rhodecode.model.db import User, ChangesetStatus
359 360
360 361 age = lambda x: _age(x)
361 362 capitalize = lambda x: x.capitalize()
362 363 email = author_email
363 364 short_id = lambda x: x[:12]
364 365 hide_credentials = lambda x: ''.join(credentials_filter(x))
365 366
366 367
367 368 def fmt_date(date):
368 369 if date:
369 370 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
370 371 return date.strftime(_fmt).decode('utf8')
371 372
372 373 return ""
373 374
374 375
375 376 def is_git(repository):
376 377 if hasattr(repository, 'alias'):
377 378 _type = repository.alias
378 379 elif hasattr(repository, 'repo_type'):
379 380 _type = repository.repo_type
380 381 else:
381 382 _type = repository
382 383 return _type == 'git'
383 384
384 385
385 386 def is_hg(repository):
386 387 if hasattr(repository, 'alias'):
387 388 _type = repository.alias
388 389 elif hasattr(repository, 'repo_type'):
389 390 _type = repository.repo_type
390 391 else:
391 392 _type = repository
392 393 return _type == 'hg'
393 394
394 395
395 396 def email_or_none(author):
396 397 _email = email(author)
397 398 if _email != '':
398 399 return _email
399 400
400 401 # See if it contains a username we can get an email from
401 402 user = User.get_by_username(author_name(author), case_insensitive=True,
402 403 cache=True)
403 404 if user is not None:
404 405 return user.email
405 406
406 407 # No valid email, not a valid user in the system, none!
407 408 return None
408 409
409 410
410 411 def person(author):
411 412 # attr to return from fetched user
412 413 person_getter = lambda usr: usr.username
413 414
414 415 # Valid email in the attribute passed, see if they're in the system
415 416 _email = email(author)
416 417 if _email != '':
417 418 user = User.get_by_email(_email, case_insensitive=True, cache=True)
418 419 if user is not None:
419 420 return person_getter(user)
420 421 return _email
421 422
422 423 # Maybe it's a username?
423 424 _author = author_name(author)
424 425 user = User.get_by_username(_author, case_insensitive=True,
425 426 cache=True)
426 427 if user is not None:
427 428 return person_getter(user)
428 429
429 430 # Still nothing? Just pass back the author name then
430 431 return _author
431 432
432 433
434 def desc_stylize(value):
435 """
436 converts tags from value into html equivalent
437
438 :param value:
439 """
440 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
441 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
442 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
443 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
444 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
445 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
446 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
447 '<div class="metatag" tag="lang">\\2</div>', value)
448 value = re.sub(r'\[([a-z]+)\]',
449 '<div class="metatag" tag="\\1">\\1</div>', value)
450
451 return value
452
453
433 454 def bool2icon(value):
434 455 """Returns True/False values represented as small html image of true/false
435 456 icons
436 457
437 458 :param value: bool value
438 459 """
439 460
440 461 if value is True:
441 462 return HTML.tag('img', src=url("/images/icons/accept.png"),
442 463 alt=_('True'))
443 464
444 465 if value is False:
445 466 return HTML.tag('img', src=url("/images/icons/cancel.png"),
446 467 alt=_('False'))
447 468
448 469 return value
449 470
450 471
451 472 def action_parser(user_log, feed=False):
452 473 """
453 474 This helper will action_map the specified string action into translated
454 475 fancy names with icons and links
455 476
456 477 :param user_log: user log instance
457 478 :param feed: use output for feeds (no html and fancy icons)
458 479 """
459 480
460 481 action = user_log.action
461 482 action_params = ' '
462 483
463 484 x = action.split(':')
464 485
465 486 if len(x) > 1:
466 487 action, action_params = x
467 488
468 489 def get_cs_links():
469 490 revs_limit = 3 # display this amount always
470 491 revs_top_limit = 50 # show upto this amount of changesets hidden
471 492 revs_ids = action_params.split(',')
472 493 deleted = user_log.repository is None
473 494 if deleted:
474 495 return ','.join(revs_ids)
475 496
476 497 repo_name = user_log.repository.repo_name
477 498
478 499 repo = user_log.repository.scm_instance
479 500
480 501 def lnk(rev, repo_name):
481 502
482 503 if isinstance(rev, BaseChangeset):
483 504 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
484 505 _url = url('changeset_home', repo_name=repo_name,
485 506 revision=rev.raw_id)
486 507 title = tooltip(rev.message)
487 508 else:
488 509 lbl = '%s' % rev
489 510 _url = '#'
490 511 title = _('Changeset not found')
491 512
492 513 return link_to(lbl, _url, title=title, class_='tooltip',)
493 514
494 515 revs = []
495 516 if len(filter(lambda v: v != '', revs_ids)) > 0:
496 517 for rev in revs_ids[:revs_top_limit]:
497 518 try:
498 519 rev = repo.get_changeset(rev)
499 520 revs.append(rev)
500 521 except ChangesetDoesNotExistError:
501 522 log.error('cannot find revision %s in this repo' % rev)
502 523 revs.append(rev)
503 524 continue
504 525 cs_links = []
505 526 cs_links.append(" " + ', '.join(
506 527 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
507 528 )
508 529 )
509 530
510 531 compare_view = (
511 532 ' <div class="compare_view tooltip" title="%s">'
512 533 '<a href="%s">%s</a> </div>' % (
513 534 _('Show all combined changesets %s->%s') % (
514 535 revs_ids[0], revs_ids[-1]
515 536 ),
516 537 url('changeset_home', repo_name=repo_name,
517 538 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
518 539 ),
519 540 _('compare view')
520 541 )
521 542 )
522 543
523 544 # if we have exactly one more than normally displayed
524 545 # just display it, takes less space than displaying
525 546 # "and 1 more revisions"
526 547 if len(revs_ids) == revs_limit + 1:
527 548 rev = revs[revs_limit]
528 549 cs_links.append(", " + lnk(rev, repo_name))
529 550
530 551 # hidden-by-default ones
531 552 if len(revs_ids) > revs_limit + 1:
532 553 uniq_id = revs_ids[0]
533 554 html_tmpl = (
534 555 '<span> %s <a class="show_more" id="_%s" '
535 556 'href="#more">%s</a> %s</span>'
536 557 )
537 558 if not feed:
538 559 cs_links.append(html_tmpl % (
539 560 _('and'),
540 561 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
541 562 _('revisions')
542 563 )
543 564 )
544 565
545 566 if not feed:
546 567 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
547 568 else:
548 569 html_tmpl = '<span id="%s"> %s </span>'
549 570
550 571 morelinks = ', '.join(
551 572 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
552 573 )
553 574
554 575 if len(revs_ids) > revs_top_limit:
555 576 morelinks += ', ...'
556 577
557 578 cs_links.append(html_tmpl % (uniq_id, morelinks))
558 579 if len(revs) > 1:
559 580 cs_links.append(compare_view)
560 581 return ''.join(cs_links)
561 582
562 583 def get_fork_name():
563 584 repo_name = action_params
564 585 return _('fork name ') + str(link_to(action_params, url('summary_home',
565 586 repo_name=repo_name,)))
566 587
567 588 def get_user_name():
568 589 user_name = action_params
569 590 return user_name
570 591
571 592 def get_users_group():
572 593 group_name = action_params
573 594 return group_name
574 595
575 596 def get_pull_request():
576 597 pull_request_id = action_params
577 598 repo_name = user_log.repository.repo_name
578 599 return link_to(_('Pull request #%s') % pull_request_id,
579 600 url('pullrequest_show', repo_name=repo_name,
580 601 pull_request_id=pull_request_id))
581 602
582 603 # action : translated str, callback(extractor), icon
583 604 action_map = {
584 605 'user_deleted_repo': (_('[deleted] repository'),
585 606 None, 'database_delete.png'),
586 607 'user_created_repo': (_('[created] repository'),
587 608 None, 'database_add.png'),
588 609 'user_created_fork': (_('[created] repository as fork'),
589 610 None, 'arrow_divide.png'),
590 611 'user_forked_repo': (_('[forked] repository'),
591 612 get_fork_name, 'arrow_divide.png'),
592 613 'user_updated_repo': (_('[updated] repository'),
593 614 None, 'database_edit.png'),
594 615 'admin_deleted_repo': (_('[delete] repository'),
595 616 None, 'database_delete.png'),
596 617 'admin_created_repo': (_('[created] repository'),
597 618 None, 'database_add.png'),
598 619 'admin_forked_repo': (_('[forked] repository'),
599 620 None, 'arrow_divide.png'),
600 621 'admin_updated_repo': (_('[updated] repository'),
601 622 None, 'database_edit.png'),
602 623 'admin_created_user': (_('[created] user'),
603 624 get_user_name, 'user_add.png'),
604 625 'admin_updated_user': (_('[updated] user'),
605 626 get_user_name, 'user_edit.png'),
606 627 'admin_created_users_group': (_('[created] users group'),
607 628 get_users_group, 'group_add.png'),
608 629 'admin_updated_users_group': (_('[updated] users group'),
609 630 get_users_group, 'group_edit.png'),
610 631 'user_commented_revision': (_('[commented] on revision in repository'),
611 632 get_cs_links, 'comment_add.png'),
612 633 'user_commented_pull_request': (_('[commented] on pull request for'),
613 634 get_pull_request, 'comment_add.png'),
614 635 'user_closed_pull_request': (_('[closed] pull request for'),
615 636 get_pull_request, 'tick.png'),
616 637 'push': (_('[pushed] into'),
617 638 get_cs_links, 'script_add.png'),
618 639 'push_local': (_('[committed via RhodeCode] into repository'),
619 640 get_cs_links, 'script_edit.png'),
620 641 'push_remote': (_('[pulled from remote] into repository'),
621 642 get_cs_links, 'connect.png'),
622 643 'pull': (_('[pulled] from'),
623 644 None, 'down_16.png'),
624 645 'started_following_repo': (_('[started following] repository'),
625 646 None, 'heart_add.png'),
626 647 'stopped_following_repo': (_('[stopped following] repository'),
627 648 None, 'heart_delete.png'),
628 649 }
629 650
630 651 action_str = action_map.get(action, action)
631 652 if feed:
632 653 action = action_str[0].replace('[', '').replace(']', '')
633 654 else:
634 655 action = action_str[0]\
635 656 .replace('[', '<span class="journal_highlight">')\
636 657 .replace(']', '</span>')
637 658
638 659 action_params_func = lambda: ""
639 660
640 661 if callable(action_str[1]):
641 662 action_params_func = action_str[1]
642 663
643 664 def action_parser_icon():
644 665 action = user_log.action
645 666 action_params = None
646 667 x = action.split(':')
647 668
648 669 if len(x) > 1:
649 670 action, action_params = x
650 671
651 672 tmpl = """<img src="%s%s" alt="%s"/>"""
652 673 ico = action_map.get(action, ['', '', ''])[2]
653 674 return literal(tmpl % ((url('/images/icons/')), ico, action))
654 675
655 676 # returned callbacks we need to call to get
656 677 return [lambda: literal(action), action_params_func, action_parser_icon]
657 678
658 679
659 680
660 681 #==============================================================================
661 682 # PERMS
662 683 #==============================================================================
663 684 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
664 685 HasRepoPermissionAny, HasRepoPermissionAll
665 686
666 687
667 688 #==============================================================================
668 689 # GRAVATAR URL
669 690 #==============================================================================
670 691
671 692 def gravatar_url(email_address, size=30):
672 693 if (not str2bool(config['app_conf'].get('use_gravatar')) or
673 694 not email_address or email_address == 'anonymous@rhodecode.org'):
674 695 f = lambda a, l: min(l, key=lambda x: abs(x - a))
675 696 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
676 697
677 698 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
678 699 default = 'identicon'
679 700 baseurl_nossl = "http://www.gravatar.com/avatar/"
680 701 baseurl_ssl = "https://secure.gravatar.com/avatar/"
681 702 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
682 703
683 704 if isinstance(email_address, unicode):
684 705 #hashlib crashes on unicode items
685 706 email_address = safe_str(email_address)
686 707 # construct the url
687 708 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
688 709 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
689 710
690 711 return gravatar_url
691 712
692 713
693 714 #==============================================================================
694 715 # REPO PAGER, PAGER FOR REPOSITORY
695 716 #==============================================================================
696 717 class RepoPage(Page):
697 718
698 719 def __init__(self, collection, page=1, items_per_page=20,
699 720 item_count=None, url=None, **kwargs):
700 721
701 722 """Create a "RepoPage" instance. special pager for paging
702 723 repository
703 724 """
704 725 self._url_generator = url
705 726
706 727 # Safe the kwargs class-wide so they can be used in the pager() method
707 728 self.kwargs = kwargs
708 729
709 730 # Save a reference to the collection
710 731 self.original_collection = collection
711 732
712 733 self.collection = collection
713 734
714 735 # The self.page is the number of the current page.
715 736 # The first page has the number 1!
716 737 try:
717 738 self.page = int(page) # make it int() if we get it as a string
718 739 except (ValueError, TypeError):
719 740 self.page = 1
720 741
721 742 self.items_per_page = items_per_page
722 743
723 744 # Unless the user tells us how many items the collections has
724 745 # we calculate that ourselves.
725 746 if item_count is not None:
726 747 self.item_count = item_count
727 748 else:
728 749 self.item_count = len(self.collection)
729 750
730 751 # Compute the number of the first and last available page
731 752 if self.item_count > 0:
732 753 self.first_page = 1
733 754 self.page_count = int(math.ceil(float(self.item_count) /
734 755 self.items_per_page))
735 756 self.last_page = self.first_page + self.page_count - 1
736 757
737 758 # Make sure that the requested page number is the range of
738 759 # valid pages
739 760 if self.page > self.last_page:
740 761 self.page = self.last_page
741 762 elif self.page < self.first_page:
742 763 self.page = self.first_page
743 764
744 765 # Note: the number of items on this page can be less than
745 766 # items_per_page if the last page is not full
746 767 self.first_item = max(0, (self.item_count) - (self.page *
747 768 items_per_page))
748 769 self.last_item = ((self.item_count - 1) - items_per_page *
749 770 (self.page - 1))
750 771
751 772 self.items = list(self.collection[self.first_item:self.last_item + 1])
752 773
753 774 # Links to previous and next page
754 775 if self.page > self.first_page:
755 776 self.previous_page = self.page - 1
756 777 else:
757 778 self.previous_page = None
758 779
759 780 if self.page < self.last_page:
760 781 self.next_page = self.page + 1
761 782 else:
762 783 self.next_page = None
763 784
764 785 # No items available
765 786 else:
766 787 self.first_page = None
767 788 self.page_count = 0
768 789 self.last_page = None
769 790 self.first_item = None
770 791 self.last_item = None
771 792 self.previous_page = None
772 793 self.next_page = None
773 794 self.items = []
774 795
775 796 # This is a subclass of the 'list' type. Initialise the list now.
776 797 list.__init__(self, reversed(self.items))
777 798
778 799
779 800 def changed_tooltip(nodes):
780 801 """
781 802 Generates a html string for changed nodes in changeset page.
782 803 It limits the output to 30 entries
783 804
784 805 :param nodes: LazyNodesGenerator
785 806 """
786 807 if nodes:
787 808 pref = ': <br/> '
788 809 suf = ''
789 810 if len(nodes) > 30:
790 811 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
791 812 return literal(pref + '<br/> '.join([safe_unicode(x.path)
792 813 for x in nodes[:30]]) + suf)
793 814 else:
794 815 return ': ' + _('No Files')
795 816
796 817
797 818 def repo_link(groups_and_repos):
798 819 """
799 820 Makes a breadcrumbs link to repo within a group
800 821 joins &raquo; on each group to create a fancy link
801 822
802 823 ex::
803 824 group >> subgroup >> repo
804 825
805 826 :param groups_and_repos:
806 827 """
807 828 groups, repo_name = groups_and_repos
808 829
809 830 if not groups:
810 831 return repo_name
811 832 else:
812 833 def make_link(group):
813 834 return link_to(group.name, url('repos_group_home',
814 835 group_name=group.group_name))
815 836 return literal(' &raquo; '.join(map(make_link, groups)) + \
816 837 " &raquo; " + repo_name)
817 838
818 839
819 840 def fancy_file_stats(stats):
820 841 """
821 842 Displays a fancy two colored bar for number of added/deleted
822 843 lines of code on file
823 844
824 845 :param stats: two element list of added/deleted lines of code
825 846 """
826 847
827 848 a, d, t = stats[0], stats[1], stats[0] + stats[1]
828 849 width = 100
829 850 unit = float(width) / (t or 1)
830 851
831 852 # needs > 9% of width to be visible or 0 to be hidden
832 853 a_p = max(9, unit * a) if a > 0 else 0
833 854 d_p = max(9, unit * d) if d > 0 else 0
834 855 p_sum = a_p + d_p
835 856
836 857 if p_sum > width:
837 858 #adjust the percentage to be == 100% since we adjusted to 9
838 859 if a_p > d_p:
839 860 a_p = a_p - (p_sum - width)
840 861 else:
841 862 d_p = d_p - (p_sum - width)
842 863
843 864 a_v = a if a > 0 else ''
844 865 d_v = d if d > 0 else ''
845 866
846 867 def cgen(l_type):
847 868 mapping = {'tr': 'top-right-rounded-corner-mid',
848 869 'tl': 'top-left-rounded-corner-mid',
849 870 'br': 'bottom-right-rounded-corner-mid',
850 871 'bl': 'bottom-left-rounded-corner-mid'}
851 872 map_getter = lambda x: mapping[x]
852 873
853 874 if l_type == 'a' and d_v:
854 875 #case when added and deleted are present
855 876 return ' '.join(map(map_getter, ['tl', 'bl']))
856 877
857 878 if l_type == 'a' and not d_v:
858 879 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
859 880
860 881 if l_type == 'd' and a_v:
861 882 return ' '.join(map(map_getter, ['tr', 'br']))
862 883
863 884 if l_type == 'd' and not a_v:
864 885 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
865 886
866 887 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
867 888 cgen('a'), a_p, a_v
868 889 )
869 890 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
870 891 cgen('d'), d_p, d_v
871 892 )
872 893 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
873 894
874 895
875 896 def urlify_text(text_):
876 897 import re
877 898
878 899 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
879 900 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
880 901
881 902 def url_func(match_obj):
882 903 url_full = match_obj.groups()[0]
883 904 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
884 905
885 906 return literal(url_pat.sub(url_func, text_))
886 907
887 908
888 909 def urlify_changesets(text_, repository):
889 910 """
890 911 Extract revision ids from changeset and make link from them
891 912
892 913 :param text_:
893 914 :param repository:
894 915 """
895 916 import re
896 917 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
897 918
898 919 def url_func(match_obj):
899 920 rev = match_obj.groups()[0]
900 921 pref = ''
901 922 if match_obj.group().startswith(' '):
902 923 pref = ' '
903 924 tmpl = (
904 925 '%(pref)s<a class="%(cls)s" href="%(url)s">'
905 926 '%(rev)s'
906 927 '</a>'
907 928 )
908 929 return tmpl % {
909 930 'pref': pref,
910 931 'cls': 'revision-link',
911 932 'url': url('changeset_home', repo_name=repository, revision=rev),
912 933 'rev': rev,
913 934 }
914 935
915 936 newtext = URL_PAT.sub(url_func, text_)
916 937
917 938 return newtext
918 939
919 940
920 941 def urlify_commit(text_, repository=None, link_=None):
921 942 """
922 943 Parses given text message and makes proper links.
923 944 issues are linked to given issue-server, and rest is a changeset link
924 945 if link_ is given, in other case it's a plain text
925 946
926 947 :param text_:
927 948 :param repository:
928 949 :param link_: changeset link
929 950 """
930 951 import re
931 952 import traceback
932 953
933 954 def escaper(string):
934 955 return string.replace('<', '&lt;').replace('>', '&gt;')
935 956
936 957 def linkify_others(t, l):
937 958 urls = re.compile(r'(\<a.*?\<\/a\>)',)
938 959 links = []
939 960 for e in urls.split(t):
940 961 if not urls.match(e):
941 962 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
942 963 else:
943 964 links.append(e)
944 965
945 966 return ''.join(links)
946 967
947 968 # urlify changesets - extrac revisions and make link out of them
948 969 text_ = urlify_changesets(escaper(text_), repository)
949 970
950 971 try:
951 972 conf = config['app_conf']
952 973
953 974 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
954 975
955 976 if URL_PAT:
956 977 ISSUE_SERVER_LNK = conf.get('issue_server_link')
957 978 ISSUE_PREFIX = conf.get('issue_prefix')
958 979
959 980 def url_func(match_obj):
960 981 pref = ''
961 982 if match_obj.group().startswith(' '):
962 983 pref = ' '
963 984
964 985 issue_id = ''.join(match_obj.groups())
965 986 tmpl = (
966 987 '%(pref)s<a class="%(cls)s" href="%(url)s">'
967 988 '%(issue-prefix)s%(id-repr)s'
968 989 '</a>'
969 990 )
970 991 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
971 992 if repository:
972 993 url = url.replace('{repo}', repository)
973 994 repo_name = repository.split(URL_SEP)[-1]
974 995 url = url.replace('{repo_name}', repo_name)
975 996 return tmpl % {
976 997 'pref': pref,
977 998 'cls': 'issue-tracker-link',
978 999 'url': url,
979 1000 'id-repr': issue_id,
980 1001 'issue-prefix': ISSUE_PREFIX,
981 1002 'serv': ISSUE_SERVER_LNK,
982 1003 }
983 1004
984 1005 newtext = URL_PAT.sub(url_func, text_)
985 1006
986 1007 if link_:
987 1008 # wrap not links into final link => link_
988 1009 newtext = linkify_others(newtext, link_)
989 1010
990 1011 return literal(newtext)
991 1012 except:
992 1013 log.error(traceback.format_exc())
993 1014 pass
994 1015
995 1016 return text_
996 1017
997 1018
998 1019 def rst(source):
999 1020 return literal('<div class="rst-block">%s</div>' %
1000 1021 MarkupRenderer.rst(source))
1001 1022
1002 1023
1003 1024 def rst_w_mentions(source):
1004 1025 """
1005 1026 Wrapped rst renderer with @mention highlighting
1006 1027
1007 1028 :param source:
1008 1029 """
1009 1030 return literal('<div class="rst-block">%s</div>' %
1010 1031 MarkupRenderer.rst_with_mentions(source))
1011 1032
1012 1033
1013 1034 def changeset_status(repo, revision):
1014 1035 return ChangesetStatusModel().get_status(repo, revision)
1015 1036
1016 1037
1017 1038 def changeset_status_lbl(changeset_status):
1018 1039 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1019 1040
1020 1041
1021 1042 def get_permission_name(key):
1022 1043 return dict(Permission.PERMS).get(key)
@@ -1,445 +1,451 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.utils
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or 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 re
27 27 from datetime import datetime
28 28 from pylons.i18n.translation import _, ungettext
29 29 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 30
31 31
32 32 def __get_lem():
33 33 """
34 34 Get language extension map based on what's inside pygments lexers
35 35 """
36 36 from pygments import lexers
37 37 from string import lower
38 38 from collections import defaultdict
39 39
40 40 d = defaultdict(lambda: [])
41 41
42 42 def __clean(s):
43 43 s = s.lstrip('*')
44 44 s = s.lstrip('.')
45 45
46 46 if s.find('[') != -1:
47 47 exts = []
48 48 start, stop = s.find('['), s.find(']')
49 49
50 50 for suffix in s[start + 1:stop]:
51 51 exts.append(s[:s.find('[')] + suffix)
52 52 return map(lower, exts)
53 53 else:
54 54 return map(lower, [s])
55 55
56 56 for lx, t in sorted(lexers.LEXERS.items()):
57 57 m = map(__clean, t[-2])
58 58 if m:
59 59 m = reduce(lambda x, y: x + y, m)
60 60 for ext in m:
61 61 desc = lx.replace('Lexer', '')
62 62 d[ext].append(desc)
63 63
64 64 return dict(d)
65 65
66 66 def str2bool(_str):
67 67 """
68 68 returs True/False value from given string, it tries to translate the
69 69 string into boolean
70 70
71 71 :param _str: string value to translate into boolean
72 72 :rtype: boolean
73 73 :returns: boolean from given string
74 74 """
75 75 if _str is None:
76 76 return False
77 77 if _str in (True, False):
78 78 return _str
79 79 _str = str(_str).strip().lower()
80 80 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 81
82 82
83 83 def convert_line_endings(line, mode):
84 84 """
85 85 Converts a given line "line end" accordingly to given mode
86 86
87 87 Available modes are::
88 88 0 - Unix
89 89 1 - Mac
90 90 2 - DOS
91 91
92 92 :param line: given line to convert
93 93 :param mode: mode to convert to
94 94 :rtype: str
95 95 :return: converted line according to mode
96 96 """
97 97 from string import replace
98 98
99 99 if mode == 0:
100 100 line = replace(line, '\r\n', '\n')
101 101 line = replace(line, '\r', '\n')
102 102 elif mode == 1:
103 103 line = replace(line, '\r\n', '\r')
104 104 line = replace(line, '\n', '\r')
105 105 elif mode == 2:
106 106 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 107 return line
108 108
109 109
110 110 def detect_mode(line, default):
111 111 """
112 112 Detects line break for given line, if line break couldn't be found
113 113 given default value is returned
114 114
115 115 :param line: str line
116 116 :param default: default
117 117 :rtype: int
118 118 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 119 """
120 120 if line.endswith('\r\n'):
121 121 return 2
122 122 elif line.endswith('\n'):
123 123 return 0
124 124 elif line.endswith('\r'):
125 125 return 1
126 126 else:
127 127 return default
128 128
129 129
130 130 def generate_api_key(username, salt=None):
131 131 """
132 132 Generates unique API key for given username, if salt is not given
133 133 it'll be generated from some random string
134 134
135 135 :param username: username as string
136 136 :param salt: salt to hash generate KEY
137 137 :rtype: str
138 138 :returns: sha1 hash from username+salt
139 139 """
140 140 from tempfile import _RandomNameSequence
141 141 import hashlib
142 142
143 143 if salt is None:
144 144 salt = _RandomNameSequence().next()
145 145
146 146 return hashlib.sha1(username + salt).hexdigest()
147 147
148 148
149 149 def safe_unicode(str_, from_encoding=None):
150 150 """
151 151 safe unicode function. Does few trick to turn str_ into unicode
152 152
153 153 In case of UnicodeDecode error we try to return it with encoding detected
154 154 by chardet library if it fails fallback to unicode with errors replaced
155 155
156 156 :param str_: string to decode
157 157 :rtype: unicode
158 158 :returns: unicode object
159 159 """
160 160 if isinstance(str_, unicode):
161 161 return str_
162 162
163 163 if not from_encoding:
164 164 import rhodecode
165 165 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
166 166 from_encoding = DEFAULT_ENCODING
167 167
168 168 try:
169 169 return unicode(str_)
170 170 except UnicodeDecodeError:
171 171 pass
172 172
173 173 try:
174 174 return unicode(str_, from_encoding)
175 175 except UnicodeDecodeError:
176 176 pass
177 177
178 178 try:
179 179 import chardet
180 180 encoding = chardet.detect(str_)['encoding']
181 181 if encoding is None:
182 182 raise Exception()
183 183 return str_.decode(encoding)
184 184 except (ImportError, UnicodeDecodeError, Exception):
185 185 return unicode(str_, from_encoding, 'replace')
186 186
187 187
188 188 def safe_str(unicode_, to_encoding=None):
189 189 """
190 190 safe str function. Does few trick to turn unicode_ into string
191 191
192 192 In case of UnicodeEncodeError we try to return it with encoding detected
193 193 by chardet library if it fails fallback to string with errors replaced
194 194
195 195 :param unicode_: unicode to encode
196 196 :rtype: str
197 197 :returns: str object
198 198 """
199 199
200 200 # if it's not basestr cast to str
201 201 if not isinstance(unicode_, basestring):
202 202 return str(unicode_)
203 203
204 204 if isinstance(unicode_, str):
205 205 return unicode_
206 206
207 207 if not to_encoding:
208 208 import rhodecode
209 209 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
210 210 to_encoding = DEFAULT_ENCODING
211 211
212 212 try:
213 213 return unicode_.encode(to_encoding)
214 214 except UnicodeEncodeError:
215 215 pass
216 216
217 217 try:
218 218 import chardet
219 219 encoding = chardet.detect(unicode_)['encoding']
220 220 if encoding is None:
221 221 raise UnicodeEncodeError()
222 222
223 223 return unicode_.encode(encoding)
224 224 except (ImportError, UnicodeEncodeError):
225 225 return unicode_.encode(to_encoding, 'replace')
226 226
227 227 return safe_str
228 228
229 229
230 230 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
231 231 """
232 232 Custom engine_from_config functions that makes sure we use NullPool for
233 233 file based sqlite databases. This prevents errors on sqlite. This only
234 234 applies to sqlalchemy versions < 0.7.0
235 235
236 236 """
237 237 import sqlalchemy
238 238 from sqlalchemy import engine_from_config as efc
239 239 import logging
240 240
241 241 if int(sqlalchemy.__version__.split('.')[1]) < 7:
242 242
243 243 # This solution should work for sqlalchemy < 0.7.0, and should use
244 244 # proxy=TimerProxy() for execution time profiling
245 245
246 246 from sqlalchemy.pool import NullPool
247 247 url = configuration[prefix + 'url']
248 248
249 249 if url.startswith('sqlite'):
250 250 kwargs.update({'poolclass': NullPool})
251 251 return efc(configuration, prefix, **kwargs)
252 252 else:
253 253 import time
254 254 from sqlalchemy import event
255 255 from sqlalchemy.engine import Engine
256 256
257 257 log = logging.getLogger('sqlalchemy.engine')
258 258 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
259 259 engine = efc(configuration, prefix, **kwargs)
260 260
261 261 def color_sql(sql):
262 262 COLOR_SEQ = "\033[1;%dm"
263 263 COLOR_SQL = YELLOW
264 264 normal = '\x1b[0m'
265 265 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
266 266
267 267 if configuration['debug']:
268 268 #attach events only for debug configuration
269 269
270 270 def before_cursor_execute(conn, cursor, statement,
271 271 parameters, context, executemany):
272 272 context._query_start_time = time.time()
273 273 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
274 274
275 275
276 276 def after_cursor_execute(conn, cursor, statement,
277 277 parameters, context, executemany):
278 278 total = time.time() - context._query_start_time
279 279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
280 280
281 281 event.listen(engine, "before_cursor_execute",
282 282 before_cursor_execute)
283 283 event.listen(engine, "after_cursor_execute",
284 284 after_cursor_execute)
285 285
286 286 return engine
287 287
288 288
289 289 def age(prevdate):
290 290 """
291 291 turns a datetime into an age string.
292 292
293 293 :param prevdate: datetime object
294 294 :rtype: unicode
295 295 :returns: unicode words describing age
296 296 """
297 297
298 298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 299 deltas = {}
300 300
301 301 # Get date parts deltas
302 302 now = datetime.now()
303 303 for part in order:
304 304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305 305
306 306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 307 # not 1 hour, -59 minutes and -59 seconds)
308 308
309 309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 310 part = order[num]
311 311 carry_part = order[num - 1]
312 312
313 313 if deltas[part] < 0:
314 314 deltas[part] += length
315 315 deltas[carry_part] -= 1
316 316
317 317 # Same thing for days except that the increment depends on the (variable)
318 318 # number of days in the month
319 319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 320 if deltas['day'] < 0:
321 321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 323 deltas['day'] += 29
324 324 else:
325 325 deltas['day'] += month_lengths[prevdate.month - 1]
326 326
327 327 deltas['month'] -= 1
328 328
329 329 if deltas['month'] < 0:
330 330 deltas['month'] += 12
331 331 deltas['year'] -= 1
332 332
333 333 # Format the result
334 334 fmt_funcs = {
335 335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 341 }
342 342
343 343 for i, part in enumerate(order):
344 344 value = deltas[part]
345 345 if value == 0:
346 346 continue
347 347
348 348 if i < 5:
349 349 sub_part = order[i + 1]
350 350 sub_value = deltas[sub_part]
351 351 else:
352 352 sub_value = 0
353 353
354 354 if sub_value == 0:
355 355 return _(u'%s ago') % fmt_funcs[part](value)
356 356
357 357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 358 fmt_funcs[sub_part](sub_value))
359 359
360 360 return _(u'just now')
361 361
362 362
363 363 def uri_filter(uri):
364 364 """
365 365 Removes user:password from given url string
366 366
367 367 :param uri:
368 368 :rtype: unicode
369 369 :returns: filtered list of strings
370 370 """
371 371 if not uri:
372 372 return ''
373 373
374 374 proto = ''
375 375
376 376 for pat in ('https://', 'http://'):
377 377 if uri.startswith(pat):
378 378 uri = uri[len(pat):]
379 379 proto = pat
380 380 break
381 381
382 382 # remove passwords and username
383 383 uri = uri[uri.find('@') + 1:]
384 384
385 385 # get the port
386 386 cred_pos = uri.find(':')
387 387 if cred_pos == -1:
388 388 host, port = uri, None
389 389 else:
390 390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391 391
392 392 return filter(None, [proto, host, port])
393 393
394 394
395 395 def credentials_filter(uri):
396 396 """
397 397 Returns a url with removed credentials
398 398
399 399 :param uri:
400 400 """
401 401
402 402 uri = uri_filter(uri)
403 403 #check if we have port
404 404 if len(uri) > 2 and uri[2]:
405 405 uri[2] = ':' + uri[2]
406 406
407 407 return ''.join(uri)
408 408
409 409
410 410 def get_changeset_safe(repo, rev):
411 411 """
412 412 Safe version of get_changeset if this changeset doesn't exists for a
413 413 repo it returns a Dummy one instead
414 414
415 415 :param repo:
416 416 :param rev:
417 417 """
418 418 from rhodecode.lib.vcs.backends.base import BaseRepository
419 419 from rhodecode.lib.vcs.exceptions import RepositoryError
420 420 if not isinstance(repo, BaseRepository):
421 421 raise Exception('You must pass an Repository '
422 422 'object as first argument got %s', type(repo))
423 423
424 424 try:
425 425 cs = repo.get_changeset(rev)
426 426 except RepositoryError:
427 427 from rhodecode.lib.utils import EmptyChangeset
428 428 cs = EmptyChangeset(requested_revision=rev)
429 429 return cs
430 430
431 431
432 432 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
433 433
434 434
435 435 def extract_mentioned_users(s):
436 436 """
437 437 Returns unique usernames from given string s that have @mention
438 438
439 439 :param s: string to get mentions
440 440 """
441 441 usrs = set()
442 442 for username in re.findall(MENTIONS_REGEX, s):
443 443 usrs.add(username)
444 444
445 445 return sorted(list(usrs), key=lambda k: k.lower())
446
447 class AttributeDict(dict):
448 def __getattr__(self, attr):
449 return self.get(attr, None)
450 __setattr__ = dict.__setitem__
451 __delattr__ = dict.__delitem__
@@ -1,1672 +1,1679 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from sqlalchemy.exc import DatabaseError
37 37 from beaker.cache import cache_region, region_invalidate
38 38 from webob.exc import HTTPNotFound
39 39
40 40 from pylons.i18n.translation import lazy_ugettext as _
41 41
42 42 from rhodecode.lib.vcs import get_backend
43 43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 44 from rhodecode.lib.vcs.exceptions import VCSError
45 45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 46
47 47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 48 safe_unicode
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model.meta import Base, Session
53 53
54 54 URL_SEP = '/'
55 55 log = logging.getLogger(__name__)
56 56
57 57 #==============================================================================
58 58 # BASE CLASSES
59 59 #==============================================================================
60 60
61 61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 62
63 63
64 64 class BaseModel(object):
65 65 """
66 66 Base Model for all classess
67 67 """
68 68
69 69 @classmethod
70 70 def _get_keys(cls):
71 71 """return column names for this model """
72 72 return class_mapper(cls).c.keys()
73 73
74 74 def get_dict(self):
75 75 """
76 76 return dict with keys and values corresponding
77 77 to this model data """
78 78
79 79 d = {}
80 80 for k in self._get_keys():
81 81 d[k] = getattr(self, k)
82 82
83 83 # also use __json__() if present to get additional fields
84 84 _json_attr = getattr(self, '__json__', None)
85 85 if _json_attr:
86 86 # update with attributes from __json__
87 87 if callable(_json_attr):
88 88 _json_attr = _json_attr()
89 89 for k, val in _json_attr.iteritems():
90 90 d[k] = val
91 91 return d
92 92
93 93 def get_appstruct(self):
94 94 """return list with keys and values tupples corresponding
95 95 to this model data """
96 96
97 97 l = []
98 98 for k in self._get_keys():
99 99 l.append((k, getattr(self, k),))
100 100 return l
101 101
102 102 def populate_obj(self, populate_dict):
103 103 """populate model with data from given populate_dict"""
104 104
105 105 for k in self._get_keys():
106 106 if k in populate_dict:
107 107 setattr(self, k, populate_dict[k])
108 108
109 109 @classmethod
110 110 def query(cls):
111 111 return Session().query(cls)
112 112
113 113 @classmethod
114 114 def get(cls, id_):
115 115 if id_:
116 116 return cls.query().get(id_)
117 117
118 118 @classmethod
119 119 def get_or_404(cls, id_):
120 120 if id_:
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 return cls.query().all()
129 129
130 130 @classmethod
131 131 def delete(cls, id_):
132 132 obj = cls.query().get(id_)
133 133 Session().delete(obj)
134 134
135 135 def __repr__(self):
136 136 if hasattr(self, '__unicode__'):
137 137 # python repr needs to return str
138 138 return safe_str(self.__unicode__())
139 139 return '<DB:%s>' % (self.__class__.__name__)
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (
145 145 UniqueConstraint('app_settings_name'),
146 146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 147 'mysql_charset': 'utf8'}
148 148 )
149 149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 152
153 153 def __init__(self, k='', v=''):
154 154 self.app_settings_name = k
155 155 self.app_settings_value = v
156 156
157 157 @validates('_app_settings_value')
158 158 def validate_settings_value(self, key, val):
159 159 assert type(val) == unicode
160 160 return val
161 161
162 162 @hybrid_property
163 163 def app_settings_value(self):
164 164 v = self._app_settings_value
165 165 if self.app_settings_name == 'ldap_active':
166 166 v = str2bool(v)
167 167 return v
168 168
169 169 @app_settings_value.setter
170 170 def app_settings_value(self, val):
171 171 """
172 172 Setter that will always make sure we use unicode in app_settings_value
173 173
174 174 :param val:
175 175 """
176 176 self._app_settings_value = safe_unicode(val)
177 177
178 178 def __unicode__(self):
179 179 return u"<%s('%s:%s')>" % (
180 180 self.__class__.__name__,
181 181 self.app_settings_name, self.app_settings_value
182 182 )
183 183
184 184 @classmethod
185 def get_by_name(cls, ldap_key):
185 def get_by_name(cls, key):
186 186 return cls.query()\
187 .filter(cls.app_settings_name == ldap_key).scalar()
187 .filter(cls.app_settings_name == key).scalar()
188
189 @classmethod
190 def get_by_name_or_create(cls, key):
191 res = cls.get_by_name(key)
192 if not res:
193 res = cls(key)
194 return res
188 195
189 196 @classmethod
190 197 def get_app_settings(cls, cache=False):
191 198
192 199 ret = cls.query()
193 200
194 201 if cache:
195 202 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196 203
197 204 if not ret:
198 205 raise Exception('Could not get application settings !')
199 206 settings = {}
200 207 for each in ret:
201 208 settings['rhodecode_' + each.app_settings_name] = \
202 209 each.app_settings_value
203 210
204 211 return settings
205 212
206 213 @classmethod
207 214 def get_ldap_settings(cls, cache=False):
208 215 ret = cls.query()\
209 216 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 217 fd = {}
211 218 for row in ret:
212 219 fd.update({row.app_settings_name: row.app_settings_value})
213 220
214 221 return fd
215 222
216 223
217 224 class RhodeCodeUi(Base, BaseModel):
218 225 __tablename__ = 'rhodecode_ui'
219 226 __table_args__ = (
220 227 UniqueConstraint('ui_key'),
221 228 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 229 'mysql_charset': 'utf8'}
223 230 )
224 231
225 232 HOOK_UPDATE = 'changegroup.update'
226 233 HOOK_REPO_SIZE = 'changegroup.repo_size'
227 234 HOOK_PUSH = 'changegroup.push_logger'
228 235 HOOK_PULL = 'preoutgoing.pull_logger'
229 236
230 237 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 238 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 239 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 240 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 241 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
235 242
236 243 @classmethod
237 244 def get_by_key(cls, key):
238 245 return cls.query().filter(cls.ui_key == key)
239 246
240 247 @classmethod
241 248 def get_builtin_hooks(cls):
242 249 q = cls.query()
243 250 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
244 251 cls.HOOK_REPO_SIZE,
245 252 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 253 return q.all()
247 254
248 255 @classmethod
249 256 def get_custom_hooks(cls):
250 257 q = cls.query()
251 258 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
252 259 cls.HOOK_REPO_SIZE,
253 260 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 261 q = q.filter(cls.ui_section == 'hooks')
255 262 return q.all()
256 263
257 264 @classmethod
258 265 def get_repos_location(cls):
259 266 return cls.get_by_key('/').one().ui_value
260 267
261 268 @classmethod
262 269 def create_or_update_hook(cls, key, val):
263 270 new_ui = cls.get_by_key(key).scalar() or cls()
264 271 new_ui.ui_section = 'hooks'
265 272 new_ui.ui_active = True
266 273 new_ui.ui_key = key
267 274 new_ui.ui_value = val
268 275
269 276 Session().add(new_ui)
270 277
271 278
272 279 class User(Base, BaseModel):
273 280 __tablename__ = 'users'
274 281 __table_args__ = (
275 282 UniqueConstraint('username'), UniqueConstraint('email'),
276 283 {'extend_existing': True, 'mysql_engine': 'InnoDB',
277 284 'mysql_charset': 'utf8'}
278 285 )
279 286 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 287 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 288 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 289 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
283 290 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 291 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 292 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 293 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 294 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 295 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 296 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 297
291 298 user_log = relationship('UserLog', cascade='all')
292 299 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293 300
294 301 repositories = relationship('Repository')
295 302 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 303 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 304 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298 305
299 306 group_member = relationship('UsersGroupMember', cascade='all')
300 307
301 308 notifications = relationship('UserNotification', cascade='all')
302 309 # notifications assigned to this user
303 310 user_created_notifications = relationship('Notification', cascade='all')
304 311 # comments created by this user
305 312 user_comments = relationship('ChangesetComment', cascade='all')
306 313 #extra emails for this user
307 314 user_emails = relationship('UserEmailMap', cascade='all')
308 315
309 316 @hybrid_property
310 317 def email(self):
311 318 return self._email
312 319
313 320 @email.setter
314 321 def email(self, val):
315 322 self._email = val.lower() if val else None
316 323
317 324 @property
318 325 def emails(self):
319 326 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
320 327 return [self.email] + [x.email for x in other]
321 328
322 329 @property
323 330 def full_name(self):
324 331 return '%s %s' % (self.name, self.lastname)
325 332
326 333 @property
327 334 def full_name_or_username(self):
328 335 return ('%s %s' % (self.name, self.lastname)
329 336 if (self.name and self.lastname) else self.username)
330 337
331 338 @property
332 339 def full_contact(self):
333 340 return '%s %s <%s>' % (self.name, self.lastname, self.email)
334 341
335 342 @property
336 343 def short_contact(self):
337 344 return '%s %s' % (self.name, self.lastname)
338 345
339 346 @property
340 347 def is_admin(self):
341 348 return self.admin
342 349
343 350 def __unicode__(self):
344 351 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
345 352 self.user_id, self.username)
346 353
347 354 @classmethod
348 355 def get_by_username(cls, username, case_insensitive=False, cache=False):
349 356 if case_insensitive:
350 357 q = cls.query().filter(cls.username.ilike(username))
351 358 else:
352 359 q = cls.query().filter(cls.username == username)
353 360
354 361 if cache:
355 362 q = q.options(FromCache(
356 363 "sql_cache_short",
357 364 "get_user_%s" % _hash_key(username)
358 365 )
359 366 )
360 367 return q.scalar()
361 368
362 369 @classmethod
363 370 def get_by_api_key(cls, api_key, cache=False):
364 371 q = cls.query().filter(cls.api_key == api_key)
365 372
366 373 if cache:
367 374 q = q.options(FromCache("sql_cache_short",
368 375 "get_api_key_%s" % api_key))
369 376 return q.scalar()
370 377
371 378 @classmethod
372 379 def get_by_email(cls, email, case_insensitive=False, cache=False):
373 380 if case_insensitive:
374 381 q = cls.query().filter(cls.email.ilike(email))
375 382 else:
376 383 q = cls.query().filter(cls.email == email)
377 384
378 385 if cache:
379 386 q = q.options(FromCache("sql_cache_short",
380 387 "get_email_key_%s" % email))
381 388
382 389 ret = q.scalar()
383 390 if ret is None:
384 391 q = UserEmailMap.query()
385 392 # try fetching in alternate email map
386 393 if case_insensitive:
387 394 q = q.filter(UserEmailMap.email.ilike(email))
388 395 else:
389 396 q = q.filter(UserEmailMap.email == email)
390 397 q = q.options(joinedload(UserEmailMap.user))
391 398 if cache:
392 399 q = q.options(FromCache("sql_cache_short",
393 400 "get_email_map_key_%s" % email))
394 401 ret = getattr(q.scalar(), 'user', None)
395 402
396 403 return ret
397 404
398 405 def update_lastlogin(self):
399 406 """Update user lastlogin"""
400 407 self.last_login = datetime.datetime.now()
401 408 Session().add(self)
402 409 log.debug('updated user %s lastlogin' % self.username)
403 410
404 411 def get_api_data(self):
405 412 """
406 413 Common function for generating user related data for API
407 414 """
408 415 user = self
409 416 data = dict(
410 417 user_id=user.user_id,
411 418 username=user.username,
412 419 firstname=user.name,
413 420 lastname=user.lastname,
414 421 email=user.email,
415 422 emails=user.emails,
416 423 api_key=user.api_key,
417 424 active=user.active,
418 425 admin=user.admin,
419 426 ldap_dn=user.ldap_dn,
420 427 last_login=user.last_login,
421 428 )
422 429 return data
423 430
424 431 def __json__(self):
425 432 data = dict(
426 433 full_name=self.full_name,
427 434 full_name_or_username=self.full_name_or_username,
428 435 short_contact=self.short_contact,
429 436 full_contact=self.full_contact
430 437 )
431 438 data.update(self.get_api_data())
432 439 return data
433 440
434 441
435 442 class UserEmailMap(Base, BaseModel):
436 443 __tablename__ = 'user_email_map'
437 444 __table_args__ = (
438 445 Index('uem_email_idx', 'email'),
439 446 UniqueConstraint('email'),
440 447 {'extend_existing': True, 'mysql_engine': 'InnoDB',
441 448 'mysql_charset': 'utf8'}
442 449 )
443 450 __mapper_args__ = {}
444 451
445 452 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
446 453 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
447 454 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
448 455
449 456 user = relationship('User', lazy='joined')
450 457
451 458 @validates('_email')
452 459 def validate_email(self, key, email):
453 460 # check if this email is not main one
454 461 main_email = Session().query(User).filter(User.email == email).scalar()
455 462 if main_email is not None:
456 463 raise AttributeError('email %s is present is user table' % email)
457 464 return email
458 465
459 466 @hybrid_property
460 467 def email(self):
461 468 return self._email
462 469
463 470 @email.setter
464 471 def email(self, val):
465 472 self._email = val.lower() if val else None
466 473
467 474
468 475 class UserLog(Base, BaseModel):
469 476 __tablename__ = 'user_logs'
470 477 __table_args__ = (
471 478 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 479 'mysql_charset': 'utf8'},
473 480 )
474 481 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
476 483 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
477 484 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 485 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 486 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
480 487 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
481 488
482 489 @property
483 490 def action_as_day(self):
484 491 return datetime.date(*self.action_date.timetuple()[:3])
485 492
486 493 user = relationship('User')
487 494 repository = relationship('Repository', cascade='')
488 495
489 496
490 497 class UsersGroup(Base, BaseModel):
491 498 __tablename__ = 'users_groups'
492 499 __table_args__ = (
493 500 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 501 'mysql_charset': 'utf8'},
495 502 )
496 503
497 504 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 505 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 506 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
500 507
501 508 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
502 509 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
503 510 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
504 511
505 512 def __unicode__(self):
506 513 return u'<userGroup(%s)>' % (self.users_group_name)
507 514
508 515 @classmethod
509 516 def get_by_group_name(cls, group_name, cache=False,
510 517 case_insensitive=False):
511 518 if case_insensitive:
512 519 q = cls.query().filter(cls.users_group_name.ilike(group_name))
513 520 else:
514 521 q = cls.query().filter(cls.users_group_name == group_name)
515 522 if cache:
516 523 q = q.options(FromCache(
517 524 "sql_cache_short",
518 525 "get_user_%s" % _hash_key(group_name)
519 526 )
520 527 )
521 528 return q.scalar()
522 529
523 530 @classmethod
524 531 def get(cls, users_group_id, cache=False):
525 532 users_group = cls.query()
526 533 if cache:
527 534 users_group = users_group.options(FromCache("sql_cache_short",
528 535 "get_users_group_%s" % users_group_id))
529 536 return users_group.get(users_group_id)
530 537
531 538 def get_api_data(self):
532 539 users_group = self
533 540
534 541 data = dict(
535 542 users_group_id=users_group.users_group_id,
536 543 group_name=users_group.users_group_name,
537 544 active=users_group.users_group_active,
538 545 )
539 546
540 547 return data
541 548
542 549
543 550 class UsersGroupMember(Base, BaseModel):
544 551 __tablename__ = 'users_groups_members'
545 552 __table_args__ = (
546 553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 554 'mysql_charset': 'utf8'},
548 555 )
549 556
550 557 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 558 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
552 559 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
553 560
554 561 user = relationship('User', lazy='joined')
555 562 users_group = relationship('UsersGroup')
556 563
557 564 def __init__(self, gr_id='', u_id=''):
558 565 self.users_group_id = gr_id
559 566 self.user_id = u_id
560 567
561 568
562 569 class Repository(Base, BaseModel):
563 570 __tablename__ = 'repositories'
564 571 __table_args__ = (
565 572 UniqueConstraint('repo_name'),
566 573 {'extend_existing': True, 'mysql_engine': 'InnoDB',
567 574 'mysql_charset': 'utf8'},
568 575 )
569 576
570 577 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 578 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
572 579 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
573 580 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
574 581 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
575 582 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
576 583 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
577 584 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
578 585 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
579 586 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
580 587 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581 588
582 589 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
583 590 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
584 591
585 592 user = relationship('User')
586 593 fork = relationship('Repository', remote_side=repo_id)
587 594 group = relationship('RepoGroup')
588 595 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
589 596 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
590 597 stats = relationship('Statistics', cascade='all', uselist=False)
591 598
592 599 followers = relationship('UserFollowing',
593 600 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
594 601 cascade='all')
595 602
596 603 logs = relationship('UserLog')
597 604 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
598 605
599 606 pull_requests_org = relationship('PullRequest',
600 607 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
601 608 cascade="all, delete, delete-orphan")
602 609
603 610 pull_requests_other = relationship('PullRequest',
604 611 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
605 612 cascade="all, delete, delete-orphan")
606 613
607 614 def __unicode__(self):
608 615 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
609 616 self.repo_name)
610 617
611 618 @classmethod
612 619 def url_sep(cls):
613 620 return URL_SEP
614 621
615 622 @classmethod
616 623 def get_by_repo_name(cls, repo_name):
617 624 q = Session().query(cls).filter(cls.repo_name == repo_name)
618 625 q = q.options(joinedload(Repository.fork))\
619 626 .options(joinedload(Repository.user))\
620 627 .options(joinedload(Repository.group))
621 628 return q.scalar()
622 629
623 630 @classmethod
624 631 def get_by_full_path(cls, repo_full_path):
625 632 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
626 633 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
627 634
628 635 @classmethod
629 636 def get_repo_forks(cls, repo_id):
630 637 return cls.query().filter(Repository.fork_id == repo_id)
631 638
632 639 @classmethod
633 640 def base_path(cls):
634 641 """
635 642 Returns base path when all repos are stored
636 643
637 644 :param cls:
638 645 """
639 646 q = Session().query(RhodeCodeUi)\
640 647 .filter(RhodeCodeUi.ui_key == cls.url_sep())
641 648 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
642 649 return q.one().ui_value
643 650
644 651 @property
645 652 def forks(self):
646 653 """
647 654 Return forks of this repo
648 655 """
649 656 return Repository.get_repo_forks(self.repo_id)
650 657
651 658 @property
652 659 def parent(self):
653 660 """
654 661 Returns fork parent
655 662 """
656 663 return self.fork
657 664
658 665 @property
659 666 def just_name(self):
660 667 return self.repo_name.split(Repository.url_sep())[-1]
661 668
662 669 @property
663 670 def groups_with_parents(self):
664 671 groups = []
665 672 if self.group is None:
666 673 return groups
667 674
668 675 cur_gr = self.group
669 676 groups.insert(0, cur_gr)
670 677 while 1:
671 678 gr = getattr(cur_gr, 'parent_group', None)
672 679 cur_gr = cur_gr.parent_group
673 680 if gr is None:
674 681 break
675 682 groups.insert(0, gr)
676 683
677 684 return groups
678 685
679 686 @property
680 687 def groups_and_repo(self):
681 688 return self.groups_with_parents, self.just_name
682 689
683 690 @LazyProperty
684 691 def repo_path(self):
685 692 """
686 693 Returns base full path for that repository means where it actually
687 694 exists on a filesystem
688 695 """
689 696 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
690 697 Repository.url_sep())
691 698 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
692 699 return q.one().ui_value
693 700
694 701 @property
695 702 def repo_full_path(self):
696 703 p = [self.repo_path]
697 704 # we need to split the name by / since this is how we store the
698 705 # names in the database, but that eventually needs to be converted
699 706 # into a valid system path
700 707 p += self.repo_name.split(Repository.url_sep())
701 708 return os.path.join(*p)
702 709
703 710 def get_new_name(self, repo_name):
704 711 """
705 712 returns new full repository name based on assigned group and new new
706 713
707 714 :param group_name:
708 715 """
709 716 path_prefix = self.group.full_path_splitted if self.group else []
710 717 return Repository.url_sep().join(path_prefix + [repo_name])
711 718
712 719 @property
713 720 def _ui(self):
714 721 """
715 722 Creates an db based ui object for this repository
716 723 """
717 724 from mercurial import ui
718 725 from mercurial import config
719 726 baseui = ui.ui()
720 727
721 728 #clean the baseui object
722 729 baseui._ocfg = config.config()
723 730 baseui._ucfg = config.config()
724 731 baseui._tcfg = config.config()
725 732
726 733 ret = RhodeCodeUi.query()\
727 734 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
728 735
729 736 hg_ui = ret
730 737 for ui_ in hg_ui:
731 738 if ui_.ui_active and ui_.ui_key != 'push_ssl':
732 739 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
733 740 ui_.ui_key, ui_.ui_value)
734 741 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
735 742
736 743 return baseui
737 744
738 745 @classmethod
739 746 def inject_ui(cls, repo, extras={}):
740 747 from rhodecode.lib.vcs.backends.hg import MercurialRepository
741 748 from rhodecode.lib.vcs.backends.git import GitRepository
742 749 required = (MercurialRepository, GitRepository)
743 750 if not isinstance(repo, required):
744 751 raise Exception('repo must be instance of %s' % required)
745 752
746 753 # inject ui extra param to log this action via push logger
747 754 for k, v in extras.items():
748 755 repo._repo.ui.setconfig('rhodecode_extras', k, v)
749 756
750 757 @classmethod
751 758 def is_valid(cls, repo_name):
752 759 """
753 760 returns True if given repo name is a valid filesystem repository
754 761
755 762 :param cls:
756 763 :param repo_name:
757 764 """
758 765 from rhodecode.lib.utils import is_valid_repo
759 766
760 767 return is_valid_repo(repo_name, cls.base_path())
761 768
762 769 def get_api_data(self):
763 770 """
764 771 Common function for generating repo api data
765 772
766 773 """
767 774 repo = self
768 775 data = dict(
769 776 repo_id=repo.repo_id,
770 777 repo_name=repo.repo_name,
771 778 repo_type=repo.repo_type,
772 779 clone_uri=repo.clone_uri,
773 780 private=repo.private,
774 781 created_on=repo.created_on,
775 782 description=repo.description,
776 783 landing_rev=repo.landing_rev,
777 784 owner=repo.user.username,
778 785 fork_of=repo.fork.repo_name if repo.fork else None
779 786 )
780 787
781 788 return data
782 789
783 790 #==========================================================================
784 791 # SCM PROPERTIES
785 792 #==========================================================================
786 793
787 794 def get_changeset(self, rev=None):
788 795 return get_changeset_safe(self.scm_instance, rev)
789 796
790 797 def get_landing_changeset(self):
791 798 """
792 799 Returns landing changeset, or if that doesn't exist returns the tip
793 800 """
794 801 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
795 802 return cs
796 803
797 804 @property
798 805 def tip(self):
799 806 return self.get_changeset('tip')
800 807
801 808 @property
802 809 def author(self):
803 810 return self.tip.author
804 811
805 812 @property
806 813 def last_change(self):
807 814 return self.scm_instance.last_change
808 815
809 816 def get_comments(self, revisions=None):
810 817 """
811 818 Returns comments for this repository grouped by revisions
812 819
813 820 :param revisions: filter query by revisions only
814 821 """
815 822 cmts = ChangesetComment.query()\
816 823 .filter(ChangesetComment.repo == self)
817 824 if revisions:
818 825 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
819 826 grouped = defaultdict(list)
820 827 for cmt in cmts.all():
821 828 grouped[cmt.revision].append(cmt)
822 829 return grouped
823 830
824 831 def statuses(self, revisions=None):
825 832 """
826 833 Returns statuses for this repository
827 834
828 835 :param revisions: list of revisions to get statuses for
829 836 :type revisions: list
830 837 """
831 838
832 839 statuses = ChangesetStatus.query()\
833 840 .filter(ChangesetStatus.repo == self)\
834 841 .filter(ChangesetStatus.version == 0)
835 842 if revisions:
836 843 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
837 844 grouped = {}
838 845
839 846 #maybe we have open new pullrequest without a status ?
840 847 stat = ChangesetStatus.STATUS_UNDER_REVIEW
841 848 status_lbl = ChangesetStatus.get_status_lbl(stat)
842 849 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
843 850 for rev in pr.revisions:
844 851 pr_id = pr.pull_request_id
845 852 pr_repo = pr.other_repo.repo_name
846 853 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
847 854
848 855 for stat in statuses.all():
849 856 pr_id = pr_repo = None
850 857 if stat.pull_request:
851 858 pr_id = stat.pull_request.pull_request_id
852 859 pr_repo = stat.pull_request.other_repo.repo_name
853 860 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
854 861 pr_id, pr_repo]
855 862 return grouped
856 863
857 864 #==========================================================================
858 865 # SCM CACHE INSTANCE
859 866 #==========================================================================
860 867
861 868 @property
862 869 def invalidate(self):
863 870 return CacheInvalidation.invalidate(self.repo_name)
864 871
865 872 def set_invalidate(self):
866 873 """
867 874 set a cache for invalidation for this instance
868 875 """
869 876 CacheInvalidation.set_invalidate(self.repo_name)
870 877
871 878 @LazyProperty
872 879 def scm_instance(self):
873 880 return self.__get_instance()
874 881
875 882 def scm_instance_cached(self, cache_map=None):
876 883 @cache_region('long_term')
877 884 def _c(repo_name):
878 885 return self.__get_instance()
879 886 rn = self.repo_name
880 887 log.debug('Getting cached instance of repo')
881 888
882 889 if cache_map:
883 890 # get using prefilled cache_map
884 891 invalidate_repo = cache_map[self.repo_name]
885 892 if invalidate_repo:
886 893 invalidate_repo = (None if invalidate_repo.cache_active
887 894 else invalidate_repo)
888 895 else:
889 896 # get from invalidate
890 897 invalidate_repo = self.invalidate
891 898
892 899 if invalidate_repo is not None:
893 900 region_invalidate(_c, None, rn)
894 901 # update our cache
895 902 CacheInvalidation.set_valid(invalidate_repo.cache_key)
896 903 return _c(rn)
897 904
898 905 def __get_instance(self):
899 906 repo_full_path = self.repo_full_path
900 907 try:
901 908 alias = get_scm(repo_full_path)[0]
902 909 log.debug('Creating instance of %s repository' % alias)
903 910 backend = get_backend(alias)
904 911 except VCSError:
905 912 log.error(traceback.format_exc())
906 913 log.error('Perhaps this repository is in db and not in '
907 914 'filesystem run rescan repositories with '
908 915 '"destroy old data " option from admin panel')
909 916 return
910 917
911 918 if alias == 'hg':
912 919
913 920 repo = backend(safe_str(repo_full_path), create=False,
914 921 baseui=self._ui)
915 922 # skip hidden web repository
916 923 if repo._get_hidden():
917 924 return
918 925 else:
919 926 repo = backend(repo_full_path, create=False)
920 927
921 928 return repo
922 929
923 930
924 931 class RepoGroup(Base, BaseModel):
925 932 __tablename__ = 'groups'
926 933 __table_args__ = (
927 934 UniqueConstraint('group_name', 'group_parent_id'),
928 935 CheckConstraint('group_id != group_parent_id'),
929 936 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 937 'mysql_charset': 'utf8'},
931 938 )
932 939 __mapper_args__ = {'order_by': 'group_name'}
933 940
934 941 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 942 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
936 943 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
937 944 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
938 945
939 946 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
940 947 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
941 948
942 949 parent_group = relationship('RepoGroup', remote_side=group_id)
943 950
944 951 def __init__(self, group_name='', parent_group=None):
945 952 self.group_name = group_name
946 953 self.parent_group = parent_group
947 954
948 955 def __unicode__(self):
949 956 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
950 957 self.group_name)
951 958
952 959 @classmethod
953 960 def groups_choices(cls):
954 961 from webhelpers.html import literal as _literal
955 962 repo_groups = [('', '')]
956 963 sep = ' &raquo; '
957 964 _name = lambda k: _literal(sep.join(k))
958 965
959 966 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
960 967 for x in cls.query().all()])
961 968
962 969 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
963 970 return repo_groups
964 971
965 972 @classmethod
966 973 def url_sep(cls):
967 974 return URL_SEP
968 975
969 976 @classmethod
970 977 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
971 978 if case_insensitive:
972 979 gr = cls.query()\
973 980 .filter(cls.group_name.ilike(group_name))
974 981 else:
975 982 gr = cls.query()\
976 983 .filter(cls.group_name == group_name)
977 984 if cache:
978 985 gr = gr.options(FromCache(
979 986 "sql_cache_short",
980 987 "get_group_%s" % _hash_key(group_name)
981 988 )
982 989 )
983 990 return gr.scalar()
984 991
985 992 @property
986 993 def parents(self):
987 994 parents_recursion_limit = 5
988 995 groups = []
989 996 if self.parent_group is None:
990 997 return groups
991 998 cur_gr = self.parent_group
992 999 groups.insert(0, cur_gr)
993 1000 cnt = 0
994 1001 while 1:
995 1002 cnt += 1
996 1003 gr = getattr(cur_gr, 'parent_group', None)
997 1004 cur_gr = cur_gr.parent_group
998 1005 if gr is None:
999 1006 break
1000 1007 if cnt == parents_recursion_limit:
1001 1008 # this will prevent accidental infinit loops
1002 1009 log.error('group nested more than %s' %
1003 1010 parents_recursion_limit)
1004 1011 break
1005 1012
1006 1013 groups.insert(0, gr)
1007 1014 return groups
1008 1015
1009 1016 @property
1010 1017 def children(self):
1011 1018 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1012 1019
1013 1020 @property
1014 1021 def name(self):
1015 1022 return self.group_name.split(RepoGroup.url_sep())[-1]
1016 1023
1017 1024 @property
1018 1025 def full_path(self):
1019 1026 return self.group_name
1020 1027
1021 1028 @property
1022 1029 def full_path_splitted(self):
1023 1030 return self.group_name.split(RepoGroup.url_sep())
1024 1031
1025 1032 @property
1026 1033 def repositories(self):
1027 1034 return Repository.query()\
1028 1035 .filter(Repository.group == self)\
1029 1036 .order_by(Repository.repo_name)
1030 1037
1031 1038 @property
1032 1039 def repositories_recursive_count(self):
1033 1040 cnt = self.repositories.count()
1034 1041
1035 1042 def children_count(group):
1036 1043 cnt = 0
1037 1044 for child in group.children:
1038 1045 cnt += child.repositories.count()
1039 1046 cnt += children_count(child)
1040 1047 return cnt
1041 1048
1042 1049 return cnt + children_count(self)
1043 1050
1044 1051 def get_new_name(self, group_name):
1045 1052 """
1046 1053 returns new full group name based on parent and new name
1047 1054
1048 1055 :param group_name:
1049 1056 """
1050 1057 path_prefix = (self.parent_group.full_path_splitted if
1051 1058 self.parent_group else [])
1052 1059 return RepoGroup.url_sep().join(path_prefix + [group_name])
1053 1060
1054 1061
1055 1062 class Permission(Base, BaseModel):
1056 1063 __tablename__ = 'permissions'
1057 1064 __table_args__ = (
1058 1065 Index('p_perm_name_idx', 'permission_name'),
1059 1066 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1060 1067 'mysql_charset': 'utf8'},
1061 1068 )
1062 1069 PERMS = [
1063 1070 ('repository.none', _('Repository no access')),
1064 1071 ('repository.read', _('Repository read access')),
1065 1072 ('repository.write', _('Repository write access')),
1066 1073 ('repository.admin', _('Repository admin access')),
1067 1074
1068 1075 ('group.none', _('Repositories Group no access')),
1069 1076 ('group.read', _('Repositories Group read access')),
1070 1077 ('group.write', _('Repositories Group write access')),
1071 1078 ('group.admin', _('Repositories Group admin access')),
1072 1079
1073 1080 ('hg.admin', _('RhodeCode Administrator')),
1074 1081 ('hg.create.none', _('Repository creation disabled')),
1075 1082 ('hg.create.repository', _('Repository creation enabled')),
1076 1083 ('hg.register.none', _('Register disabled')),
1077 1084 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1078 1085 'with manual activation')),
1079 1086
1080 1087 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1081 1088 'with auto activation')),
1082 1089 ]
1083 1090
1084 1091 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1085 1092 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1086 1093 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1087 1094
1088 1095 def __unicode__(self):
1089 1096 return u"<%s('%s:%s')>" % (
1090 1097 self.__class__.__name__, self.permission_id, self.permission_name
1091 1098 )
1092 1099
1093 1100 @classmethod
1094 1101 def get_by_key(cls, key):
1095 1102 return cls.query().filter(cls.permission_name == key).scalar()
1096 1103
1097 1104 @classmethod
1098 1105 def get_default_perms(cls, default_user_id):
1099 1106 q = Session().query(UserRepoToPerm, Repository, cls)\
1100 1107 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1101 1108 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1102 1109 .filter(UserRepoToPerm.user_id == default_user_id)
1103 1110
1104 1111 return q.all()
1105 1112
1106 1113 @classmethod
1107 1114 def get_default_group_perms(cls, default_user_id):
1108 1115 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1109 1116 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1110 1117 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1111 1118 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1112 1119
1113 1120 return q.all()
1114 1121
1115 1122
1116 1123 class UserRepoToPerm(Base, BaseModel):
1117 1124 __tablename__ = 'repo_to_perm'
1118 1125 __table_args__ = (
1119 1126 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1120 1127 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1121 1128 'mysql_charset': 'utf8'}
1122 1129 )
1123 1130 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1124 1131 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1125 1132 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1126 1133 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1127 1134
1128 1135 user = relationship('User')
1129 1136 repository = relationship('Repository')
1130 1137 permission = relationship('Permission')
1131 1138
1132 1139 @classmethod
1133 1140 def create(cls, user, repository, permission):
1134 1141 n = cls()
1135 1142 n.user = user
1136 1143 n.repository = repository
1137 1144 n.permission = permission
1138 1145 Session().add(n)
1139 1146 return n
1140 1147
1141 1148 def __unicode__(self):
1142 1149 return u'<user:%s => %s >' % (self.user, self.repository)
1143 1150
1144 1151
1145 1152 class UserToPerm(Base, BaseModel):
1146 1153 __tablename__ = 'user_to_perm'
1147 1154 __table_args__ = (
1148 1155 UniqueConstraint('user_id', 'permission_id'),
1149 1156 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1150 1157 'mysql_charset': 'utf8'}
1151 1158 )
1152 1159 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1153 1160 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1154 1161 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1155 1162
1156 1163 user = relationship('User')
1157 1164 permission = relationship('Permission', lazy='joined')
1158 1165
1159 1166
1160 1167 class UsersGroupRepoToPerm(Base, BaseModel):
1161 1168 __tablename__ = 'users_group_repo_to_perm'
1162 1169 __table_args__ = (
1163 1170 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1164 1171 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1165 1172 'mysql_charset': 'utf8'}
1166 1173 )
1167 1174 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1168 1175 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1169 1176 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1170 1177 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1171 1178
1172 1179 users_group = relationship('UsersGroup')
1173 1180 permission = relationship('Permission')
1174 1181 repository = relationship('Repository')
1175 1182
1176 1183 @classmethod
1177 1184 def create(cls, users_group, repository, permission):
1178 1185 n = cls()
1179 1186 n.users_group = users_group
1180 1187 n.repository = repository
1181 1188 n.permission = permission
1182 1189 Session().add(n)
1183 1190 return n
1184 1191
1185 1192 def __unicode__(self):
1186 1193 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1187 1194
1188 1195
1189 1196 class UsersGroupToPerm(Base, BaseModel):
1190 1197 __tablename__ = 'users_group_to_perm'
1191 1198 __table_args__ = (
1192 1199 UniqueConstraint('users_group_id', 'permission_id',),
1193 1200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1194 1201 'mysql_charset': 'utf8'}
1195 1202 )
1196 1203 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1197 1204 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1198 1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1199 1206
1200 1207 users_group = relationship('UsersGroup')
1201 1208 permission = relationship('Permission')
1202 1209
1203 1210
1204 1211 class UserRepoGroupToPerm(Base, BaseModel):
1205 1212 __tablename__ = 'user_repo_group_to_perm'
1206 1213 __table_args__ = (
1207 1214 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1208 1215 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1209 1216 'mysql_charset': 'utf8'}
1210 1217 )
1211 1218
1212 1219 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1213 1220 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1214 1221 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1215 1222 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1216 1223
1217 1224 user = relationship('User')
1218 1225 group = relationship('RepoGroup')
1219 1226 permission = relationship('Permission')
1220 1227
1221 1228
1222 1229 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1223 1230 __tablename__ = 'users_group_repo_group_to_perm'
1224 1231 __table_args__ = (
1225 1232 UniqueConstraint('users_group_id', 'group_id'),
1226 1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1227 1234 'mysql_charset': 'utf8'}
1228 1235 )
1229 1236
1230 1237 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1231 1238 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1232 1239 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1233 1240 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1234 1241
1235 1242 users_group = relationship('UsersGroup')
1236 1243 permission = relationship('Permission')
1237 1244 group = relationship('RepoGroup')
1238 1245
1239 1246
1240 1247 class Statistics(Base, BaseModel):
1241 1248 __tablename__ = 'statistics'
1242 1249 __table_args__ = (
1243 1250 UniqueConstraint('repository_id'),
1244 1251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1245 1252 'mysql_charset': 'utf8'}
1246 1253 )
1247 1254 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1248 1255 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1249 1256 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1250 1257 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1251 1258 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1252 1259 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1253 1260
1254 1261 repository = relationship('Repository', single_parent=True)
1255 1262
1256 1263
1257 1264 class UserFollowing(Base, BaseModel):
1258 1265 __tablename__ = 'user_followings'
1259 1266 __table_args__ = (
1260 1267 UniqueConstraint('user_id', 'follows_repository_id'),
1261 1268 UniqueConstraint('user_id', 'follows_user_id'),
1262 1269 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1263 1270 'mysql_charset': 'utf8'}
1264 1271 )
1265 1272
1266 1273 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1267 1274 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1268 1275 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1269 1276 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1270 1277 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1271 1278
1272 1279 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1273 1280
1274 1281 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1275 1282 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1276 1283
1277 1284 @classmethod
1278 1285 def get_repo_followers(cls, repo_id):
1279 1286 return cls.query().filter(cls.follows_repo_id == repo_id)
1280 1287
1281 1288
1282 1289 class CacheInvalidation(Base, BaseModel):
1283 1290 __tablename__ = 'cache_invalidation'
1284 1291 __table_args__ = (
1285 1292 UniqueConstraint('cache_key'),
1286 1293 Index('key_idx', 'cache_key'),
1287 1294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1288 1295 'mysql_charset': 'utf8'},
1289 1296 )
1290 1297 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1291 1298 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1292 1299 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1293 1300 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1294 1301
1295 1302 def __init__(self, cache_key, cache_args=''):
1296 1303 self.cache_key = cache_key
1297 1304 self.cache_args = cache_args
1298 1305 self.cache_active = False
1299 1306
1300 1307 def __unicode__(self):
1301 1308 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1302 1309 self.cache_id, self.cache_key)
1303 1310
1304 1311 @classmethod
1305 1312 def clear_cache(cls):
1306 1313 cls.query().delete()
1307 1314
1308 1315 @classmethod
1309 1316 def _get_key(cls, key):
1310 1317 """
1311 1318 Wrapper for generating a key, together with a prefix
1312 1319
1313 1320 :param key:
1314 1321 """
1315 1322 import rhodecode
1316 1323 prefix = ''
1317 1324 iid = rhodecode.CONFIG.get('instance_id')
1318 1325 if iid:
1319 1326 prefix = iid
1320 1327 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1321 1328
1322 1329 @classmethod
1323 1330 def get_by_key(cls, key):
1324 1331 return cls.query().filter(cls.cache_key == key).scalar()
1325 1332
1326 1333 @classmethod
1327 1334 def _get_or_create_key(cls, key, prefix, org_key):
1328 1335 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1329 1336 if not inv_obj:
1330 1337 try:
1331 1338 inv_obj = CacheInvalidation(key, org_key)
1332 1339 Session().add(inv_obj)
1333 1340 Session().commit()
1334 1341 except Exception:
1335 1342 log.error(traceback.format_exc())
1336 1343 Session().rollback()
1337 1344 return inv_obj
1338 1345
1339 1346 @classmethod
1340 1347 def invalidate(cls, key):
1341 1348 """
1342 1349 Returns Invalidation object if this given key should be invalidated
1343 1350 None otherwise. `cache_active = False` means that this cache
1344 1351 state is not valid and needs to be invalidated
1345 1352
1346 1353 :param key:
1347 1354 """
1348 1355
1349 1356 key, _prefix, _org_key = cls._get_key(key)
1350 1357 inv = cls._get_or_create_key(key, _prefix, _org_key)
1351 1358
1352 1359 if inv and inv.cache_active is False:
1353 1360 return inv
1354 1361
1355 1362 @classmethod
1356 1363 def set_invalidate(cls, key):
1357 1364 """
1358 1365 Mark this Cache key for invalidation
1359 1366
1360 1367 :param key:
1361 1368 """
1362 1369
1363 1370 key, _prefix, _org_key = cls._get_key(key)
1364 1371 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1365 1372 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1366 1373 _org_key))
1367 1374 try:
1368 1375 for inv_obj in inv_objs:
1369 1376 if inv_obj:
1370 1377 inv_obj.cache_active = False
1371 1378
1372 1379 Session().add(inv_obj)
1373 1380 Session().commit()
1374 1381 except Exception:
1375 1382 log.error(traceback.format_exc())
1376 1383 Session().rollback()
1377 1384
1378 1385 @classmethod
1379 1386 def set_valid(cls, key):
1380 1387 """
1381 1388 Mark this cache key as active and currently cached
1382 1389
1383 1390 :param key:
1384 1391 """
1385 1392 inv_obj = cls.get_by_key(key)
1386 1393 inv_obj.cache_active = True
1387 1394 Session().add(inv_obj)
1388 1395 Session().commit()
1389 1396
1390 1397 @classmethod
1391 1398 def get_cache_map(cls):
1392 1399
1393 1400 class cachemapdict(dict):
1394 1401
1395 1402 def __init__(self, *args, **kwargs):
1396 1403 fixkey = kwargs.get('fixkey')
1397 1404 if fixkey:
1398 1405 del kwargs['fixkey']
1399 1406 self.fixkey = fixkey
1400 1407 super(cachemapdict, self).__init__(*args, **kwargs)
1401 1408
1402 1409 def __getattr__(self, name):
1403 1410 key = name
1404 1411 if self.fixkey:
1405 1412 key, _prefix, _org_key = cls._get_key(key)
1406 1413 if key in self.__dict__:
1407 1414 return self.__dict__[key]
1408 1415 else:
1409 1416 return self[key]
1410 1417
1411 1418 def __getitem__(self, key):
1412 1419 if self.fixkey:
1413 1420 key, _prefix, _org_key = cls._get_key(key)
1414 1421 try:
1415 1422 return super(cachemapdict, self).__getitem__(key)
1416 1423 except KeyError:
1417 1424 return
1418 1425
1419 1426 cache_map = cachemapdict(fixkey=True)
1420 1427 for obj in cls.query().all():
1421 1428 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1422 1429 return cache_map
1423 1430
1424 1431
1425 1432 class ChangesetComment(Base, BaseModel):
1426 1433 __tablename__ = 'changeset_comments'
1427 1434 __table_args__ = (
1428 1435 Index('cc_revision_idx', 'revision'),
1429 1436 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1430 1437 'mysql_charset': 'utf8'},
1431 1438 )
1432 1439 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1433 1440 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1434 1441 revision = Column('revision', String(40), nullable=True)
1435 1442 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1436 1443 line_no = Column('line_no', Unicode(10), nullable=True)
1437 1444 f_path = Column('f_path', Unicode(1000), nullable=True)
1438 1445 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1439 1446 text = Column('text', Unicode(25000), nullable=False)
1440 1447 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1441 1448 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1442 1449
1443 1450 author = relationship('User', lazy='joined')
1444 1451 repo = relationship('Repository')
1445 1452 status_change = relationship('ChangesetStatus', uselist=False)
1446 1453 pull_request = relationship('PullRequest', lazy='joined')
1447 1454
1448 1455 @classmethod
1449 1456 def get_users(cls, revision=None, pull_request_id=None):
1450 1457 """
1451 1458 Returns user associated with this ChangesetComment. ie those
1452 1459 who actually commented
1453 1460
1454 1461 :param cls:
1455 1462 :param revision:
1456 1463 """
1457 1464 q = Session().query(User)\
1458 1465 .join(ChangesetComment.author)
1459 1466 if revision:
1460 1467 q = q.filter(cls.revision == revision)
1461 1468 elif pull_request_id:
1462 1469 q = q.filter(cls.pull_request_id == pull_request_id)
1463 1470 return q.all()
1464 1471
1465 1472
1466 1473 class ChangesetStatus(Base, BaseModel):
1467 1474 __tablename__ = 'changeset_statuses'
1468 1475 __table_args__ = (
1469 1476 Index('cs_revision_idx', 'revision'),
1470 1477 Index('cs_version_idx', 'version'),
1471 1478 UniqueConstraint('repo_id', 'revision', 'version'),
1472 1479 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 1480 'mysql_charset': 'utf8'}
1474 1481 )
1475 1482 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1476 1483 STATUS_APPROVED = 'approved'
1477 1484 STATUS_REJECTED = 'rejected'
1478 1485 STATUS_UNDER_REVIEW = 'under_review'
1479 1486
1480 1487 STATUSES = [
1481 1488 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1482 1489 (STATUS_APPROVED, _("Approved")),
1483 1490 (STATUS_REJECTED, _("Rejected")),
1484 1491 (STATUS_UNDER_REVIEW, _("Under Review")),
1485 1492 ]
1486 1493
1487 1494 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1488 1495 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1489 1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1490 1497 revision = Column('revision', String(40), nullable=False)
1491 1498 status = Column('status', String(128), nullable=False, default=DEFAULT)
1492 1499 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1493 1500 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1494 1501 version = Column('version', Integer(), nullable=False, default=0)
1495 1502 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1496 1503
1497 1504 author = relationship('User', lazy='joined')
1498 1505 repo = relationship('Repository')
1499 1506 comment = relationship('ChangesetComment', lazy='joined')
1500 1507 pull_request = relationship('PullRequest', lazy='joined')
1501 1508
1502 1509 def __unicode__(self):
1503 1510 return u"<%s('%s:%s')>" % (
1504 1511 self.__class__.__name__,
1505 1512 self.status, self.author
1506 1513 )
1507 1514
1508 1515 @classmethod
1509 1516 def get_status_lbl(cls, value):
1510 1517 return dict(cls.STATUSES).get(value)
1511 1518
1512 1519 @property
1513 1520 def status_lbl(self):
1514 1521 return ChangesetStatus.get_status_lbl(self.status)
1515 1522
1516 1523
1517 1524 class PullRequest(Base, BaseModel):
1518 1525 __tablename__ = 'pull_requests'
1519 1526 __table_args__ = (
1520 1527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1521 1528 'mysql_charset': 'utf8'},
1522 1529 )
1523 1530
1524 1531 STATUS_NEW = u'new'
1525 1532 STATUS_OPEN = u'open'
1526 1533 STATUS_CLOSED = u'closed'
1527 1534
1528 1535 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1529 1536 title = Column('title', Unicode(256), nullable=True)
1530 1537 description = Column('description', UnicodeText(10240), nullable=True)
1531 1538 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1532 1539 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1533 1540 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1534 1541 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1535 1542 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1536 1543 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1537 1544 org_ref = Column('org_ref', Unicode(256), nullable=False)
1538 1545 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1539 1546 other_ref = Column('other_ref', Unicode(256), nullable=False)
1540 1547
1541 1548 @hybrid_property
1542 1549 def revisions(self):
1543 1550 return self._revisions.split(':')
1544 1551
1545 1552 @revisions.setter
1546 1553 def revisions(self, val):
1547 1554 self._revisions = ':'.join(val)
1548 1555
1549 1556 author = relationship('User', lazy='joined')
1550 1557 reviewers = relationship('PullRequestReviewers',
1551 1558 cascade="all, delete, delete-orphan")
1552 1559 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1553 1560 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1554 1561 statuses = relationship('ChangesetStatus')
1555 1562 comments = relationship('ChangesetComment',
1556 1563 cascade="all, delete, delete-orphan")
1557 1564
1558 1565 def is_closed(self):
1559 1566 return self.status == self.STATUS_CLOSED
1560 1567
1561 1568 def __json__(self):
1562 1569 return dict(
1563 1570 revisions=self.revisions
1564 1571 )
1565 1572
1566 1573
1567 1574 class PullRequestReviewers(Base, BaseModel):
1568 1575 __tablename__ = 'pull_request_reviewers'
1569 1576 __table_args__ = (
1570 1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 1578 'mysql_charset': 'utf8'},
1572 1579 )
1573 1580
1574 1581 def __init__(self, user=None, pull_request=None):
1575 1582 self.user = user
1576 1583 self.pull_request = pull_request
1577 1584
1578 1585 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1579 1586 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1580 1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1581 1588
1582 1589 user = relationship('User')
1583 1590 pull_request = relationship('PullRequest')
1584 1591
1585 1592
1586 1593 class Notification(Base, BaseModel):
1587 1594 __tablename__ = 'notifications'
1588 1595 __table_args__ = (
1589 1596 Index('notification_type_idx', 'type'),
1590 1597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1591 1598 'mysql_charset': 'utf8'},
1592 1599 )
1593 1600
1594 1601 TYPE_CHANGESET_COMMENT = u'cs_comment'
1595 1602 TYPE_MESSAGE = u'message'
1596 1603 TYPE_MENTION = u'mention'
1597 1604 TYPE_REGISTRATION = u'registration'
1598 1605 TYPE_PULL_REQUEST = u'pull_request'
1599 1606 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1600 1607
1601 1608 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1602 1609 subject = Column('subject', Unicode(512), nullable=True)
1603 1610 body = Column('body', UnicodeText(50000), nullable=True)
1604 1611 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1605 1612 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1606 1613 type_ = Column('type', Unicode(256))
1607 1614
1608 1615 created_by_user = relationship('User')
1609 1616 notifications_to_users = relationship('UserNotification', lazy='joined',
1610 1617 cascade="all, delete, delete-orphan")
1611 1618
1612 1619 @property
1613 1620 def recipients(self):
1614 1621 return [x.user for x in UserNotification.query()\
1615 1622 .filter(UserNotification.notification == self)\
1616 1623 .order_by(UserNotification.user_id.asc()).all()]
1617 1624
1618 1625 @classmethod
1619 1626 def create(cls, created_by, subject, body, recipients, type_=None):
1620 1627 if type_ is None:
1621 1628 type_ = Notification.TYPE_MESSAGE
1622 1629
1623 1630 notification = cls()
1624 1631 notification.created_by_user = created_by
1625 1632 notification.subject = subject
1626 1633 notification.body = body
1627 1634 notification.type_ = type_
1628 1635 notification.created_on = datetime.datetime.now()
1629 1636
1630 1637 for u in recipients:
1631 1638 assoc = UserNotification()
1632 1639 assoc.notification = notification
1633 1640 u.notifications.append(assoc)
1634 1641 Session().add(notification)
1635 1642 return notification
1636 1643
1637 1644 @property
1638 1645 def description(self):
1639 1646 from rhodecode.model.notification import NotificationModel
1640 1647 return NotificationModel().make_description(self)
1641 1648
1642 1649
1643 1650 class UserNotification(Base, BaseModel):
1644 1651 __tablename__ = 'user_to_notification'
1645 1652 __table_args__ = (
1646 1653 UniqueConstraint('user_id', 'notification_id'),
1647 1654 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1648 1655 'mysql_charset': 'utf8'}
1649 1656 )
1650 1657 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1651 1658 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1652 1659 read = Column('read', Boolean, default=False)
1653 1660 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1654 1661
1655 1662 user = relationship('User', lazy="joined")
1656 1663 notification = relationship('Notification', lazy="joined",
1657 1664 order_by=lambda: Notification.created_on.desc(),)
1658 1665
1659 1666 def mark_as_read(self):
1660 1667 self.read = True
1661 1668 Session().add(self)
1662 1669
1663 1670
1664 1671 class DbMigrateVersion(Base, BaseModel):
1665 1672 __tablename__ = 'db_migrate_version'
1666 1673 __table_args__ = (
1667 1674 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1668 1675 'mysql_charset': 'utf8'},
1669 1676 )
1670 1677 repository_id = Column('repository_id', String(250), primary_key=True)
1671 1678 repository_path = Column('repository_path', Text)
1672 1679 version = Column('version', Integer)
@@ -1,310 +1,317 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import logging
23 23
24 24 import formencode
25 25 from formencode import All
26 26
27 27 from pylons.i18n.translation import _
28 28
29 29 from rhodecode.model import validators as v
30 30 from rhodecode import BACKENDS
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 class LoginForm(formencode.Schema):
36 36 allow_extra_fields = True
37 37 filter_extra_fields = True
38 38 username = v.UnicodeString(
39 39 strip=True,
40 40 min=1,
41 41 not_empty=True,
42 42 messages={
43 43 'empty': _(u'Please enter a login'),
44 44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 45 )
46 46
47 47 password = v.UnicodeString(
48 48 strip=False,
49 49 min=3,
50 50 not_empty=True,
51 51 messages={
52 52 'empty': _(u'Please enter a password'),
53 53 'tooShort': _(u'Enter %(min)i characters or more')}
54 54 )
55 55
56 56 remember = v.StringBoolean(if_missing=False)
57 57
58 58 chained_validators = [v.ValidAuth()]
59 59
60 60
61 61 def UserForm(edit=False, old_data={}):
62 62 class _UserForm(formencode.Schema):
63 63 allow_extra_fields = True
64 64 filter_extra_fields = True
65 65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 66 v.ValidUsername(edit, old_data))
67 67 if edit:
68 68 new_password = All(
69 69 v.ValidPassword(),
70 70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 71 )
72 72 password_confirmation = All(
73 73 v.ValidPassword(),
74 74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 75 )
76 76 admin = v.StringBoolean(if_missing=False)
77 77 else:
78 78 password = All(
79 79 v.ValidPassword(),
80 80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 81 )
82 82 password_confirmation = All(
83 83 v.ValidPassword(),
84 84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 85 )
86 86
87 87 active = v.StringBoolean(if_missing=False)
88 88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91 91
92 92 chained_validators = [v.ValidPasswordsMatch()]
93 93
94 94 return _UserForm
95 95
96 96
97 97 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
98 98 class _UsersGroupForm(formencode.Schema):
99 99 allow_extra_fields = True
100 100 filter_extra_fields = True
101 101
102 102 users_group_name = All(
103 103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 104 v.ValidUsersGroup(edit, old_data)
105 105 )
106 106
107 107 users_group_active = v.StringBoolean(if_missing=False)
108 108
109 109 if edit:
110 110 users_group_members = v.OneOf(
111 111 available_members, hideList=False, testValueList=True,
112 112 if_missing=None, not_empty=False
113 113 )
114 114
115 115 return _UsersGroupForm
116 116
117 117
118 118 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
119 119 class _ReposGroupForm(formencode.Schema):
120 120 allow_extra_fields = True
121 121 filter_extra_fields = False
122 122
123 123 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 124 v.SlugifyName())
125 125 group_description = v.UnicodeString(strip=True, min=1,
126 126 not_empty=True)
127 127 group_parent_id = v.OneOf(available_groups, hideList=False,
128 128 testValueList=True,
129 129 if_missing=None, not_empty=False)
130 130
131 131 chained_validators = [v.ValidReposGroup(edit, old_data),
132 132 v.ValidPerms('group')]
133 133
134 134 return _ReposGroupForm
135 135
136 136
137 137 def RegisterForm(edit=False, old_data={}):
138 138 class _RegisterForm(formencode.Schema):
139 139 allow_extra_fields = True
140 140 filter_extra_fields = True
141 141 username = All(
142 142 v.ValidUsername(edit, old_data),
143 143 v.UnicodeString(strip=True, min=1, not_empty=True)
144 144 )
145 145 password = All(
146 146 v.ValidPassword(),
147 147 v.UnicodeString(strip=False, min=6, not_empty=True)
148 148 )
149 149 password_confirmation = All(
150 150 v.ValidPassword(),
151 151 v.UnicodeString(strip=False, min=6, not_empty=True)
152 152 )
153 153 active = v.StringBoolean(if_missing=False)
154 154 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
155 155 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
156 156 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
157 157
158 158 chained_validators = [v.ValidPasswordsMatch()]
159 159
160 160 return _RegisterForm
161 161
162 162
163 163 def PasswordResetForm():
164 164 class _PasswordResetForm(formencode.Schema):
165 165 allow_extra_fields = True
166 166 filter_extra_fields = True
167 167 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
168 168 return _PasswordResetForm
169 169
170 170
171 171 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
172 172 repo_groups=[], landing_revs=[]):
173 173 class _RepoForm(formencode.Schema):
174 174 allow_extra_fields = True
175 175 filter_extra_fields = False
176 176 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
177 177 v.SlugifyName())
178 178 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
179 179 repo_group = v.OneOf(repo_groups, hideList=True)
180 180 repo_type = v.OneOf(supported_backends)
181 181 description = v.UnicodeString(strip=True, min=1, not_empty=False)
182 182 private = v.StringBoolean(if_missing=False)
183 183 enable_statistics = v.StringBoolean(if_missing=False)
184 184 enable_downloads = v.StringBoolean(if_missing=False)
185 185 landing_rev = v.OneOf(landing_revs, hideList=True)
186 186
187 187 if edit:
188 188 #this is repo owner
189 189 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
190 190
191 191 chained_validators = [v.ValidCloneUri(),
192 192 v.ValidRepoName(edit, old_data),
193 193 v.ValidPerms()]
194 194 return _RepoForm
195 195
196 196
197 197 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
198 198 repo_groups=[], landing_revs=[]):
199 199 class _RepoForkForm(formencode.Schema):
200 200 allow_extra_fields = True
201 201 filter_extra_fields = False
202 202 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
203 203 v.SlugifyName())
204 204 repo_group = v.OneOf(repo_groups, hideList=True)
205 205 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
206 206 description = v.UnicodeString(strip=True, min=1, not_empty=True)
207 207 private = v.StringBoolean(if_missing=False)
208 208 copy_permissions = v.StringBoolean(if_missing=False)
209 209 update_after_clone = v.StringBoolean(if_missing=False)
210 210 fork_parent_id = v.UnicodeString()
211 211 chained_validators = [v.ValidForkName(edit, old_data)]
212 212 landing_rev = v.OneOf(landing_revs, hideList=True)
213 213
214 214 return _RepoForkForm
215 215
216 216
217 217 def RepoSettingsForm(edit=False, old_data={},
218 218 supported_backends=BACKENDS.keys(), repo_groups=[],
219 219 landing_revs=[]):
220 220 class _RepoForm(formencode.Schema):
221 221 allow_extra_fields = True
222 222 filter_extra_fields = False
223 223 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
224 224 v.SlugifyName())
225 225 description = v.UnicodeString(strip=True, min=1, not_empty=True)
226 226 repo_group = v.OneOf(repo_groups, hideList=True)
227 227 private = v.StringBoolean(if_missing=False)
228 228 landing_rev = v.OneOf(landing_revs, hideList=True)
229 229 chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(),
230 230 v.ValidSettings()]
231 231 return _RepoForm
232 232
233 233
234 234 def ApplicationSettingsForm():
235 235 class _ApplicationSettingsForm(formencode.Schema):
236 236 allow_extra_fields = True
237 237 filter_extra_fields = False
238 238 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
239 239 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
240 240 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
241 241
242 242 return _ApplicationSettingsForm
243 243
244 244
245 def ApplicationVisualisationForm():
246 class _ApplicationVisualisationForm(formencode.Schema):
247 allow_extra_fields = True
248 filter_extra_fields = False
249 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
250 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
251 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
252
253 return _ApplicationVisualisationForm
254
255
245 256 def ApplicationUiSettingsForm():
246 257 class _ApplicationUiSettingsForm(formencode.Schema):
247 258 allow_extra_fields = True
248 259 filter_extra_fields = False
249 web_push_ssl = v.OneOf(['true', 'false'], if_missing='false')
260 web_push_ssl = v.StringBoolean(if_missing=False)
250 261 paths_root_path = All(
251 262 v.ValidPath(),
252 263 v.UnicodeString(strip=True, min=1, not_empty=True)
253 264 )
254 hooks_changegroup_update = v.OneOf(['True', 'False'],
255 if_missing=False)
256 hooks_changegroup_repo_size = v.OneOf(['True', 'False'],
257 if_missing=False)
258 hooks_changegroup_push_logger = v.OneOf(['True', 'False'],
259 if_missing=False)
260 hooks_preoutgoing_pull_logger = v.OneOf(['True', 'False'],
261 if_missing=False)
265 hooks_changegroup_update = v.StringBoolean(if_missing=False)
266 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
267 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
268 hooks_preoutgoing_pull_logger = v.StringBoolean(if_missing=False)
262 269
263 270 return _ApplicationUiSettingsForm
264 271
265 272
266 273 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
267 274 class _DefaultPermissionsForm(formencode.Schema):
268 275 allow_extra_fields = True
269 276 filter_extra_fields = True
270 277 overwrite_default = v.StringBoolean(if_missing=False)
271 anonymous = v.OneOf(['True', 'False'], if_missing=False)
278 anonymous = v.StringBoolean(if_missing=False)
272 279 default_perm = v.OneOf(perms_choices)
273 280 default_register = v.OneOf(register_choices)
274 281 default_create = v.OneOf(create_choices)
275 282
276 283 return _DefaultPermissionsForm
277 284
278 285
279 286 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
280 287 tls_kind_choices):
281 288 class _LdapSettingsForm(formencode.Schema):
282 289 allow_extra_fields = True
283 290 filter_extra_fields = True
284 291 #pre_validators = [LdapLibValidator]
285 292 ldap_active = v.StringBoolean(if_missing=False)
286 293 ldap_host = v.UnicodeString(strip=True,)
287 294 ldap_port = v.Number(strip=True,)
288 295 ldap_tls_kind = v.OneOf(tls_kind_choices)
289 296 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
290 297 ldap_dn_user = v.UnicodeString(strip=True,)
291 298 ldap_dn_pass = v.UnicodeString(strip=True,)
292 299 ldap_base_dn = v.UnicodeString(strip=True,)
293 300 ldap_filter = v.UnicodeString(strip=True,)
294 301 ldap_search_scope = v.OneOf(search_scope_choices)
295 302 ldap_attr_login = All(
296 303 v.AttrLoginValidator(),
297 304 v.UnicodeString(strip=True,)
298 305 )
299 306 ldap_attr_firstname = v.UnicodeString(strip=True,)
300 307 ldap_attr_lastname = v.UnicodeString(strip=True,)
301 308 ldap_attr_email = v.UnicodeString(strip=True,)
302 309
303 310 return _LdapSettingsForm
304 311
305 312
306 313 def UserExtraEmailForm():
307 314 class _UserExtraEmailForm(formencode.Schema):
308 315 email = All(v.UniqSystemEmail(), v.Email)
309 316
310 317 return _UserExtraEmailForm
@@ -1,4560 +1,4635 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 2 {
3 3 border: 0;
4 4 outline: 0;
5 5 font-size: 100%;
6 6 vertical-align: baseline;
7 7 background: transparent;
8 8 margin: 0;
9 9 padding: 0;
10 10 }
11 11
12 12 body {
13 13 line-height: 1;
14 14 height: 100%;
15 15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 18 color: #000;
19 19 margin: 0;
20 20 padding: 0;
21 21 font-size: 12px;
22 22 }
23 23
24 24 ol,ul {
25 25 list-style: none;
26 26 }
27 27
28 28 blockquote,q {
29 29 quotes: none;
30 30 }
31 31
32 32 blockquote:before,blockquote:after,q:before,q:after {
33 33 content: none;
34 34 }
35 35
36 36 :focus {
37 37 outline: 0;
38 38 }
39 39
40 40 del {
41 41 text-decoration: line-through;
42 42 }
43 43
44 44 table {
45 45 border-collapse: collapse;
46 46 border-spacing: 0;
47 47 }
48 48
49 49 html {
50 50 height: 100%;
51 51 }
52 52
53 53 a {
54 54 color: #003367;
55 55 text-decoration: none;
56 56 cursor: pointer;
57 57 }
58 58
59 59 a:hover {
60 60 color: #316293;
61 61 text-decoration: underline;
62 62 }
63 63
64 64 h1,h2,h3,h4,h5,h6 {
65 65 color: #292929;
66 66 font-weight: 700;
67 67 }
68 68
69 69 h1 {
70 70 font-size: 22px;
71 71 }
72 72
73 73 h2 {
74 74 font-size: 20px;
75 75 }
76 76
77 77 h3 {
78 78 font-size: 18px;
79 79 }
80 80
81 81 h4 {
82 82 font-size: 16px;
83 83 }
84 84
85 85 h5 {
86 86 font-size: 14px;
87 87 }
88 88
89 89 h6 {
90 90 font-size: 11px;
91 91 }
92 92
93 93 ul.circle {
94 94 list-style-type: circle;
95 95 }
96 96
97 97 ul.disc {
98 98 list-style-type: disc;
99 99 }
100 100
101 101 ul.square {
102 102 list-style-type: square;
103 103 }
104 104
105 105 ol.lower-roman {
106 106 list-style-type: lower-roman;
107 107 }
108 108
109 109 ol.upper-roman {
110 110 list-style-type: upper-roman;
111 111 }
112 112
113 113 ol.lower-alpha {
114 114 list-style-type: lower-alpha;
115 115 }
116 116
117 117 ol.upper-alpha {
118 118 list-style-type: upper-alpha;
119 119 }
120 120
121 121 ol.decimal {
122 122 list-style-type: decimal;
123 123 }
124 124
125 125 div.color {
126 126 clear: both;
127 127 overflow: hidden;
128 128 position: absolute;
129 129 background: #FFF;
130 130 margin: 7px 0 0 60px;
131 131 padding: 1px 1px 1px 0;
132 132 }
133 133
134 134 div.color a {
135 135 width: 15px;
136 136 height: 15px;
137 137 display: block;
138 138 float: left;
139 139 margin: 0 0 0 1px;
140 140 padding: 0;
141 141 }
142 142
143 143 div.options {
144 144 clear: both;
145 145 overflow: hidden;
146 146 position: absolute;
147 147 background: #FFF;
148 148 margin: 7px 0 0 162px;
149 149 padding: 0;
150 150 }
151 151
152 152 div.options a {
153 153 height: 1%;
154 154 display: block;
155 155 text-decoration: none;
156 156 margin: 0;
157 157 padding: 3px 8px;
158 158 }
159 159
160 160 .top-left-rounded-corner {
161 161 -webkit-border-top-left-radius: 8px;
162 162 -khtml-border-radius-topleft: 8px;
163 163 -moz-border-radius-topleft: 8px;
164 164 border-top-left-radius: 8px;
165 165 }
166 166
167 167 .top-right-rounded-corner {
168 168 -webkit-border-top-right-radius: 8px;
169 169 -khtml-border-radius-topright: 8px;
170 170 -moz-border-radius-topright: 8px;
171 171 border-top-right-radius: 8px;
172 172 }
173 173
174 174 .bottom-left-rounded-corner {
175 175 -webkit-border-bottom-left-radius: 8px;
176 176 -khtml-border-radius-bottomleft: 8px;
177 177 -moz-border-radius-bottomleft: 8px;
178 178 border-bottom-left-radius: 8px;
179 179 }
180 180
181 181 .bottom-right-rounded-corner {
182 182 -webkit-border-bottom-right-radius: 8px;
183 183 -khtml-border-radius-bottomright: 8px;
184 184 -moz-border-radius-bottomright: 8px;
185 185 border-bottom-right-radius: 8px;
186 186 }
187 187
188 188 .top-left-rounded-corner-mid {
189 189 -webkit-border-top-left-radius: 4px;
190 190 -khtml-border-radius-topleft: 4px;
191 191 -moz-border-radius-topleft: 4px;
192 192 border-top-left-radius: 4px;
193 193 }
194 194
195 195 .top-right-rounded-corner-mid {
196 196 -webkit-border-top-right-radius: 4px;
197 197 -khtml-border-radius-topright: 4px;
198 198 -moz-border-radius-topright: 4px;
199 199 border-top-right-radius: 4px;
200 200 }
201 201
202 202 .bottom-left-rounded-corner-mid {
203 203 -webkit-border-bottom-left-radius: 4px;
204 204 -khtml-border-radius-bottomleft: 4px;
205 205 -moz-border-radius-bottomleft: 4px;
206 206 border-bottom-left-radius: 4px;
207 207 }
208 208
209 209 .bottom-right-rounded-corner-mid {
210 210 -webkit-border-bottom-right-radius: 4px;
211 211 -khtml-border-radius-bottomright: 4px;
212 212 -moz-border-radius-bottomright: 4px;
213 213 border-bottom-right-radius: 4px;
214 214 }
215 215
216 216 .help-block {
217 217 color: #999999;
218 218 display: block;
219 219 margin-bottom: 0;
220 220 margin-top: 5px;
221 221 }
222 222 a.permalink{
223 223 visibility: hidden;
224 224 }
225 225
226 226 a.permalink:hover{
227 227 text-decoration: none;
228 228 }
229 229
230 230 h1:hover > a.permalink,
231 231 h2:hover > a.permalink,
232 232 h3:hover > a.permalink,
233 233 h4:hover > a.permalink,
234 234 h5:hover > a.permalink,
235 235 h6:hover > a.permalink,
236 236 div:hover > a.permalink {
237 237 visibility: visible;
238 238 }
239 239
240 240 #header {
241 241 margin: 0;
242 242 padding: 0 10px;
243 243 }
244 244
245 245 #header ul#logged-user {
246 246 margin-bottom: 5px !important;
247 247 -webkit-border-radius: 0px 0px 8px 8px;
248 248 -khtml-border-radius: 0px 0px 8px 8px;
249 249 -moz-border-radius: 0px 0px 8px 8px;
250 250 border-radius: 0px 0px 8px 8px;
251 251 height: 37px;
252 252 background-color: #003B76;
253 253 background-repeat: repeat-x;
254 254 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
255 255 background-image: -moz-linear-gradient(top, #003b76, #00376e);
256 256 background-image: -ms-linear-gradient(top, #003b76, #00376e);
257 257 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
258 258 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
259 259 background-image: -o-linear-gradient(top, #003b76, #00376e);
260 260 background-image: linear-gradient(top, #003b76, #00376e);
261 261 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
262 262 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
263 263 }
264 264
265 265 #header ul#logged-user li {
266 266 list-style: none;
267 267 float: left;
268 268 margin: 8px 0 0;
269 269 padding: 4px 12px;
270 270 border-left: 1px solid #316293;
271 271 }
272 272
273 273 #header ul#logged-user li.first {
274 274 border-left: none;
275 275 margin: 4px;
276 276 }
277 277
278 278 #header ul#logged-user li.first div.gravatar {
279 279 margin-top: -2px;
280 280 }
281 281
282 282 #header ul#logged-user li.first div.account {
283 283 padding-top: 4px;
284 284 float: left;
285 285 }
286 286
287 287 #header ul#logged-user li.last {
288 288 border-right: none;
289 289 }
290 290
291 291 #header ul#logged-user li a {
292 292 color: #fff;
293 293 font-weight: 700;
294 294 text-decoration: none;
295 295 }
296 296
297 297 #header ul#logged-user li a:hover {
298 298 text-decoration: underline;
299 299 }
300 300
301 301 #header ul#logged-user li.highlight a {
302 302 color: #fff;
303 303 }
304 304
305 305 #header ul#logged-user li.highlight a:hover {
306 306 color: #FFF;
307 307 }
308 308
309 309 #header #header-inner {
310 310 min-height: 44px;
311 311 clear: both;
312 312 position: relative;
313 313 background-color: #003B76;
314 314 background-repeat: repeat-x;
315 315 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
316 316 background-image: -moz-linear-gradient(top, #003b76, #00376e);
317 317 background-image: -ms-linear-gradient(top, #003b76, #00376e);
318 318 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
319 319 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
320 320 background-image: -o-linear-gradient(top, #003b76, #00376e);
321 321 background-image: linear-gradient(top, #003b76, #00376e);
322 322 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
323 323 margin: 0;
324 324 padding: 0;
325 325 display: block;
326 326 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
327 327 -webkit-border-radius: 4px 4px 4px 4px;
328 328 -khtml-border-radius: 4px 4px 4px 4px;
329 329 -moz-border-radius: 4px 4px 4px 4px;
330 330 border-radius: 4px 4px 4px 4px;
331 331 }
332 332 #header #header-inner.hover{
333 333 position: fixed !important;
334 334 width: 100% !important;
335 335 margin-left: -10px !important;
336 336 z-index: 10000;
337 337 -webkit-border-radius: 0px 0px 0px 0px;
338 338 -khtml-border-radius: 0px 0px 0px 0px;
339 339 -moz-border-radius: 0px 0px 0px 0px;
340 340 border-radius: 0px 0px 0px 0px;
341 341 }
342 342
343 343 .ie7 #header #header-inner.hover,
344 344 .ie8 #header #header-inner.hover,
345 345 .ie9 #header #header-inner.hover
346 346 {
347 347 z-index: auto !important;
348 348 }
349 349
350 350 .header-pos-fix{
351 351 margin-top: -44px;
352 352 padding-top: 44px;
353 353 }
354 354
355 355 #header #header-inner #home a {
356 356 height: 40px;
357 357 width: 46px;
358 358 display: block;
359 359 background: url("../images/button_home.png");
360 360 background-position: 0 0;
361 361 margin: 0;
362 362 padding: 0;
363 363 }
364 364
365 365 #header #header-inner #home a:hover {
366 366 background-position: 0 -40px;
367 367 }
368 368
369 369 #header #header-inner #logo {
370 370 float: left;
371 371 position: absolute;
372 372 }
373 373
374 374 #header #header-inner #logo h1 {
375 375 color: #FFF;
376 376 font-size: 20px;
377 377 margin: 12px 0 0 13px;
378 378 padding: 0;
379 379 }
380 380
381 381 #header #header-inner #logo a {
382 382 color: #fff;
383 383 text-decoration: none;
384 384 }
385 385
386 386 #header #header-inner #logo a:hover {
387 387 color: #bfe3ff;
388 388 }
389 389
390 390 #header #header-inner #quick,#header #header-inner #quick ul {
391 391 position: relative;
392 392 float: right;
393 393 list-style-type: none;
394 394 list-style-position: outside;
395 395 margin: 8px 8px 0 0;
396 396 padding: 0;
397 397 }
398 398
399 399 #header #header-inner #quick li {
400 400 position: relative;
401 401 float: left;
402 402 margin: 0 5px 0 0;
403 403 padding: 0;
404 404 }
405 405
406 406 #header #header-inner #quick li a.menu_link {
407 407 top: 0;
408 408 left: 0;
409 409 height: 1%;
410 410 display: block;
411 411 clear: both;
412 412 overflow: hidden;
413 413 color: #FFF;
414 414 font-weight: 700;
415 415 text-decoration: none;
416 416 background: #369;
417 417 padding: 0;
418 418 -webkit-border-radius: 4px 4px 4px 4px;
419 419 -khtml-border-radius: 4px 4px 4px 4px;
420 420 -moz-border-radius: 4px 4px 4px 4px;
421 421 border-radius: 4px 4px 4px 4px;
422 422 }
423 423
424 424 #header #header-inner #quick li span.short {
425 425 padding: 9px 6px 8px 6px;
426 426 }
427 427
428 428 #header #header-inner #quick li span {
429 429 top: 0;
430 430 right: 0;
431 431 height: 1%;
432 432 display: block;
433 433 float: left;
434 434 border-left: 1px solid #3f6f9f;
435 435 margin: 0;
436 436 padding: 10px 12px 8px 10px;
437 437 }
438 438
439 439 #header #header-inner #quick li span.normal {
440 440 border: none;
441 441 padding: 10px 12px 8px;
442 442 }
443 443
444 444 #header #header-inner #quick li span.icon {
445 445 top: 0;
446 446 left: 0;
447 447 border-left: none;
448 448 border-right: 1px solid #2e5c89;
449 449 padding: 8px 6px 4px;
450 450 }
451 451
452 452 #header #header-inner #quick li span.icon_short {
453 453 top: 0;
454 454 left: 0;
455 455 border-left: none;
456 456 border-right: 1px solid #2e5c89;
457 457 padding: 8px 6px 4px;
458 458 }
459 459
460 460 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
461 461 {
462 462 margin: 0px -2px 0px 0px;
463 463 }
464 464
465 465 #header #header-inner #quick li a:hover {
466 466 background: #4e4e4e no-repeat top left;
467 467 }
468 468
469 469 #header #header-inner #quick li a:hover span {
470 470 border-left: 1px solid #545454;
471 471 }
472 472
473 473 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
474 474 {
475 475 border-left: none;
476 476 border-right: 1px solid #464646;
477 477 }
478 478
479 479 #header #header-inner #quick ul {
480 480 top: 29px;
481 481 right: 0;
482 482 min-width: 200px;
483 483 display: none;
484 484 position: absolute;
485 485 background: #FFF;
486 486 border: 1px solid #666;
487 487 border-top: 1px solid #003367;
488 488 z-index: 100;
489 489 margin: 0px 0px 0px 0px;
490 490 padding: 0;
491 491 }
492 492
493 493 #header #header-inner #quick ul.repo_switcher {
494 494 max-height: 275px;
495 495 overflow-x: hidden;
496 496 overflow-y: auto;
497 497 }
498 498
499 499 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
500 500 float: none;
501 501 margin: 0;
502 502 border-bottom: 2px solid #003367;
503 503 }
504 504
505 505 #header #header-inner #quick .repo_switcher_type {
506 506 position: absolute;
507 507 left: 0;
508 508 top: 9px;
509 509 }
510 510
511 511 #header #header-inner #quick li ul li {
512 512 border-bottom: 1px solid #ddd;
513 513 }
514 514
515 515 #header #header-inner #quick li ul li a {
516 516 width: 182px;
517 517 height: auto;
518 518 display: block;
519 519 float: left;
520 520 background: #FFF;
521 521 color: #003367;
522 522 font-weight: 400;
523 523 margin: 0;
524 524 padding: 7px 9px;
525 525 }
526 526
527 527 #header #header-inner #quick li ul li a:hover {
528 528 color: #000;
529 529 background: #FFF;
530 530 }
531 531
532 532 #header #header-inner #quick ul ul {
533 533 top: auto;
534 534 }
535 535
536 536 #header #header-inner #quick li ul ul {
537 537 right: 200px;
538 538 max-height: 275px;
539 539 overflow: auto;
540 540 overflow-x: hidden;
541 541 white-space: normal;
542 542 }
543 543
544 544 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
545 545 {
546 546 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
547 547 #FFF;
548 548 width: 167px;
549 549 margin: 0;
550 550 padding: 12px 9px 7px 24px;
551 551 }
552 552
553 553 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
554 554 {
555 555 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
556 556 #FFF;
557 557 min-width: 167px;
558 558 margin: 0;
559 559 padding: 12px 9px 7px 24px;
560 560 }
561 561
562 562 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
563 563 {
564 564 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
565 565 9px #FFF;
566 566 min-width: 167px;
567 567 margin: 0;
568 568 padding: 12px 9px 7px 24px;
569 569 }
570 570
571 571 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
572 572 {
573 573 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
574 574 #FFF;
575 575 min-width: 167px;
576 576 margin: 0 0 0 14px;
577 577 padding: 12px 9px 7px 24px;
578 578 }
579 579
580 580 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
581 581 {
582 582 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
583 583 #FFF;
584 584 min-width: 167px;
585 585 margin: 0 0 0 14px;
586 586 padding: 12px 9px 7px 24px;
587 587 }
588 588
589 589 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
590 590 {
591 591 background: url("../images/icons/database_edit.png") no-repeat scroll
592 592 4px 9px #FFF;
593 593 width: 167px;
594 594 margin: 0;
595 595 padding: 12px 9px 7px 24px;
596 596 }
597 597
598 598 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
599 599 {
600 600 background: url("../images/icons/database_link.png") no-repeat scroll
601 601 4px 9px #FFF;
602 602 width: 167px;
603 603 margin: 0;
604 604 padding: 12px 9px 7px 24px;
605 605 }
606 606
607 607 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
608 608 {
609 609 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
610 610 width: 167px;
611 611 margin: 0;
612 612 padding: 12px 9px 7px 24px;
613 613 }
614 614
615 615 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
616 616 {
617 617 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
618 618 width: 167px;
619 619 margin: 0;
620 620 padding: 12px 9px 7px 24px;
621 621 }
622 622
623 623 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
624 624 {
625 625 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
626 626 width: 167px;
627 627 margin: 0;
628 628 padding: 12px 9px 7px 24px;
629 629 }
630 630
631 631 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
632 632 {
633 633 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
634 634 width: 167px;
635 635 margin: 0;
636 636 padding: 12px 9px 7px 24px;
637 637 }
638 638
639 639 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
640 640 {
641 641 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
642 642 width: 167px;
643 643 margin: 0;
644 644 padding: 12px 9px 7px 24px;
645 645 }
646 646
647 647 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
648 648 {
649 649 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
650 650 9px;
651 651 width: 167px;
652 652 margin: 0;
653 653 padding: 12px 9px 7px 24px;
654 654 }
655 655
656 656 #header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover
657 657 {
658 658 background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px
659 659 9px;
660 660 width: 167px;
661 661 margin: 0;
662 662 padding: 12px 9px 7px 24px;
663 663 }
664 664
665 665 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
666 666 {
667 667 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
668 668 width: 167px;
669 669 margin: 0;
670 670 padding: 12px 9px 7px 24px;
671 671 }
672 672
673 673 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
674 674 {
675 675 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
676 676 width: 167px;
677 677 margin: 0;
678 678 padding: 12px 9px 7px 24px;
679 679 }
680 680
681 681 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
682 682 {
683 683 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
684 684 9px;
685 685 width: 167px;
686 686 margin: 0;
687 687 padding: 12px 9px 7px 24px;
688 688 }
689 689
690 690 #header #header-inner #quick li ul li a.tags,
691 691 #header #header-inner #quick li ul li a.tags:hover{
692 692 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
693 693 width: 167px;
694 694 margin: 0;
695 695 padding: 12px 9px 7px 24px;
696 696 }
697 697
698 698 #header #header-inner #quick li ul li a.bookmarks,
699 699 #header #header-inner #quick li ul li a.bookmarks:hover{
700 700 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
701 701 width: 167px;
702 702 margin: 0;
703 703 padding: 12px 9px 7px 24px;
704 704 }
705 705
706 706 #header #header-inner #quick li ul li a.admin,
707 707 #header #header-inner #quick li ul li a.admin:hover{
708 708 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
709 709 width: 167px;
710 710 margin: 0;
711 711 padding: 12px 9px 7px 24px;
712 712 }
713 713
714 714 .groups_breadcrumbs a {
715 715 color: #fff;
716 716 }
717 717
718 718 .groups_breadcrumbs a:hover {
719 719 color: #bfe3ff;
720 720 text-decoration: none;
721 721 }
722 722
723 723 td.quick_repo_menu {
724 724 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
725 725 cursor: pointer;
726 726 width: 8px;
727 727 border: 1px solid transparent;
728 728 }
729 729
730 730 td.quick_repo_menu.active {
731 731 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
732 732 border: 1px solid #003367;
733 733 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
734 734 cursor: pointer;
735 735 }
736 736
737 737 td.quick_repo_menu .menu_items {
738 738 margin-top: 10px;
739 739 margin-left:-6px;
740 740 width: 150px;
741 741 position: absolute;
742 742 background-color: #FFF;
743 743 background: none repeat scroll 0 0 #FFFFFF;
744 744 border-color: #003367 #666666 #666666;
745 745 border-right: 1px solid #666666;
746 746 border-style: solid;
747 747 border-width: 1px;
748 748 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
749 749 border-top-style: none;
750 750 }
751 751
752 752 td.quick_repo_menu .menu_items li {
753 753 padding: 0 !important;
754 754 }
755 755
756 756 td.quick_repo_menu .menu_items a {
757 757 display: block;
758 758 padding: 4px 12px 4px 8px;
759 759 }
760 760
761 761 td.quick_repo_menu .menu_items a:hover {
762 762 background-color: #EEE;
763 763 text-decoration: none;
764 764 }
765 765
766 766 td.quick_repo_menu .menu_items .icon img {
767 767 margin-bottom: -2px;
768 768 }
769 769
770 770 td.quick_repo_menu .menu_items.hidden {
771 771 display: none;
772 772 }
773 773
774 774 .yui-dt-first th {
775 775 text-align: left;
776 776 }
777 777
778 778 /*
779 779 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
780 780 Code licensed under the BSD License:
781 781 http://developer.yahoo.com/yui/license.html
782 782 version: 2.9.0
783 783 */
784 784 .yui-skin-sam .yui-dt-mask {
785 785 position: absolute;
786 786 z-index: 9500;
787 787 }
788 788 .yui-dt-tmp {
789 789 position: absolute;
790 790 left: -9000px;
791 791 }
792 792 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
793 793 .yui-dt-scrollable .yui-dt-hd {
794 794 overflow: hidden;
795 795 position: relative;
796 796 }
797 797 .yui-dt-scrollable .yui-dt-bd thead tr,
798 798 .yui-dt-scrollable .yui-dt-bd thead th {
799 799 position: absolute;
800 800 left: -1500px;
801 801 }
802 802 .yui-dt-scrollable tbody { -moz-outline: 0 }
803 803 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
804 804 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
805 805 .yui-dt-coltarget {
806 806 position: absolute;
807 807 z-index: 999;
808 808 }
809 809 .yui-dt-hd { zoom: 1 }
810 810 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
811 811 .yui-dt-resizer {
812 812 position: absolute;
813 813 right: 0;
814 814 bottom: 0;
815 815 height: 100%;
816 816 cursor: e-resize;
817 817 cursor: col-resize;
818 818 background-color: #CCC;
819 819 opacity: 0;
820 820 filter: alpha(opacity=0);
821 821 }
822 822 .yui-dt-resizerproxy {
823 823 visibility: hidden;
824 824 position: absolute;
825 825 z-index: 9000;
826 826 background-color: #CCC;
827 827 opacity: 0;
828 828 filter: alpha(opacity=0);
829 829 }
830 830 th.yui-dt-hidden .yui-dt-liner,
831 831 td.yui-dt-hidden .yui-dt-liner,
832 832 th.yui-dt-hidden .yui-dt-resizer { display: none }
833 833 .yui-dt-editor,
834 834 .yui-dt-editor-shim {
835 835 position: absolute;
836 836 z-index: 9000;
837 837 }
838 838 .yui-skin-sam .yui-dt table {
839 839 margin: 0;
840 840 padding: 0;
841 841 font-family: arial;
842 842 font-size: inherit;
843 843 border-collapse: separate;
844 844 *border-collapse: collapse;
845 845 border-spacing: 0;
846 846 border: 1px solid #7f7f7f;
847 847 }
848 848 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
849 849 .yui-skin-sam .yui-dt caption {
850 850 color: #000;
851 851 font-size: 85%;
852 852 font-weight: normal;
853 853 font-style: italic;
854 854 line-height: 1;
855 855 padding: 1em 0;
856 856 text-align: center;
857 857 }
858 858 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
859 859 .yui-skin-sam .yui-dt th,
860 860 .yui-skin-sam .yui-dt th a {
861 861 font-weight: normal;
862 862 text-decoration: none;
863 863 color: #000;
864 864 vertical-align: bottom;
865 865 }
866 866 .yui-skin-sam .yui-dt th {
867 867 margin: 0;
868 868 padding: 0;
869 869 border: 0;
870 870 border-right: 1px solid #cbcbcb;
871 871 }
872 872 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
873 873 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
874 874 .yui-skin-sam .yui-dt-liner {
875 875 margin: 0;
876 876 padding: 0;
877 877 }
878 878 .yui-skin-sam .yui-dt-coltarget {
879 879 width: 5px;
880 880 background-color: red;
881 881 }
882 882 .yui-skin-sam .yui-dt td {
883 883 margin: 0;
884 884 padding: 0;
885 885 border: 0;
886 886 border-right: 1px solid #cbcbcb;
887 887 text-align: left;
888 888 }
889 889 .yui-skin-sam .yui-dt-list td { border-right: 0 }
890 890 .yui-skin-sam .yui-dt-resizer { width: 6px }
891 891 .yui-skin-sam .yui-dt-mask {
892 892 background-color: #000;
893 893 opacity: .25;
894 894 filter: alpha(opacity=25);
895 895 }
896 896 .yui-skin-sam .yui-dt-message { background-color: #FFF }
897 897 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
898 898 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
899 899 border-left: 1px solid #7f7f7f;
900 900 border-top: 1px solid #7f7f7f;
901 901 border-right: 1px solid #7f7f7f;
902 902 }
903 903 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
904 904 border-left: 1px solid #7f7f7f;
905 905 border-bottom: 1px solid #7f7f7f;
906 906 border-right: 1px solid #7f7f7f;
907 907 background-color: #FFF;
908 908 }
909 909 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
910 910 .yui-skin-sam th.yui-dt-asc,
911 911 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
912 912 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
913 913 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
914 914 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
915 915 tbody .yui-dt-editable { cursor: pointer }
916 916 .yui-dt-editor {
917 917 text-align: left;
918 918 background-color: #f2f2f2;
919 919 border: 1px solid #808080;
920 920 padding: 6px;
921 921 }
922 922 .yui-dt-editor label {
923 923 padding-left: 4px;
924 924 padding-right: 6px;
925 925 }
926 926 .yui-dt-editor .yui-dt-button {
927 927 padding-top: 6px;
928 928 text-align: right;
929 929 }
930 930 .yui-dt-editor .yui-dt-button button {
931 931 background: url(../images/sprite.png) repeat-x 0 0;
932 932 border: 1px solid #999;
933 933 width: 4em;
934 934 height: 1.8em;
935 935 margin-left: 6px;
936 936 }
937 937 .yui-dt-editor .yui-dt-button button.yui-dt-default {
938 938 background: url(../images/sprite.png) repeat-x 0 -1400px;
939 939 background-color: #5584e0;
940 940 border: 1px solid #304369;
941 941 color: #FFF;
942 942 }
943 943 .yui-dt-editor .yui-dt-button button:hover {
944 944 background: url(../images/sprite.png) repeat-x 0 -1300px;
945 945 color: #000;
946 946 }
947 947 .yui-dt-editor .yui-dt-button button:active {
948 948 background: url(../images/sprite.png) repeat-x 0 -1700px;
949 949 color: #000;
950 950 }
951 951 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
952 952 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
953 953 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
954 954 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
955 955 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
956 956 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
957 957 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
958 958 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
959 959 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
960 960 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
961 961 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
962 962 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
963 963 .yui-skin-sam th.yui-dt-highlighted,
964 964 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
965 965 .yui-skin-sam tr.yui-dt-highlighted,
966 966 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
967 967 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
968 968 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
969 969 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
970 970 cursor: pointer;
971 971 background-color: #b2d2ff;
972 972 }
973 973 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
974 974 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
975 975 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
976 976 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
977 977 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
978 978 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
979 979 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
980 980 cursor: pointer;
981 981 background-color: #b2d2ff;
982 982 }
983 983 .yui-skin-sam th.yui-dt-selected,
984 984 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
985 985 .yui-skin-sam tr.yui-dt-selected td,
986 986 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
987 987 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
988 988 background-color: #426fd9;
989 989 color: #FFF;
990 990 }
991 991 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
992 992 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
993 993 background-color: #446cd7;
994 994 color: #FFF;
995 995 }
996 996 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
997 997 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
998 998 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
999 999 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
1000 1000 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
1001 1001 background-color: #426fd9;
1002 1002 color: #FFF;
1003 1003 }
1004 1004 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
1005 1005 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
1006 1006 background-color: #446cd7;
1007 1007 color: #FFF;
1008 1008 }
1009 1009 .yui-skin-sam .yui-dt-paginator {
1010 1010 display: block;
1011 1011 margin: 6px 0;
1012 1012 white-space: nowrap;
1013 1013 }
1014 1014 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
1015 1015 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
1016 1016 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
1017 1017 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
1018 1018 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
1019 1019 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
1020 1020 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
1021 1021 .yui-skin-sam a.yui-dt-page {
1022 1022 border: 1px solid #cbcbcb;
1023 1023 padding: 2px 6px;
1024 1024 text-decoration: none;
1025 1025 background-color: #fff;
1026 1026 }
1027 1027 .yui-skin-sam .yui-dt-selected {
1028 1028 border: 1px solid #fff;
1029 1029 background-color: #fff;
1030 1030 }
1031 1031
1032 1032 #content #left {
1033 1033 left: 0;
1034 1034 width: 280px;
1035 1035 position: absolute;
1036 1036 }
1037 1037
1038 1038 #content #right {
1039 1039 margin: 0 60px 10px 290px;
1040 1040 }
1041 1041
1042 1042 #content div.box {
1043 1043 clear: both;
1044 1044 overflow: hidden;
1045 1045 background: #fff;
1046 1046 margin: 0 0 10px;
1047 1047 padding: 0 0 10px;
1048 1048 -webkit-border-radius: 4px 4px 4px 4px;
1049 1049 -khtml-border-radius: 4px 4px 4px 4px;
1050 1050 -moz-border-radius: 4px 4px 4px 4px;
1051 1051 border-radius: 4px 4px 4px 4px;
1052 1052 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1053 1053 }
1054 1054
1055 1055 #content div.box-left {
1056 1056 width: 49%;
1057 1057 clear: none;
1058 1058 float: left;
1059 1059 margin: 0 0 10px;
1060 1060 }
1061 1061
1062 1062 #content div.box-right {
1063 1063 width: 49%;
1064 1064 clear: none;
1065 1065 float: right;
1066 1066 margin: 0 0 10px;
1067 1067 }
1068 1068
1069 1069 #content div.box div.title {
1070 1070 clear: both;
1071 1071 overflow: hidden;
1072 1072 background-color: #003B76;
1073 1073 background-repeat: repeat-x;
1074 1074 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1075 1075 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1076 1076 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1077 1077 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1078 1078 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1079 1079 background-image: -o-linear-gradient(top, #003b76, #00376e);
1080 1080 background-image: linear-gradient(top, #003b76, #00376e);
1081 1081 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1082 1082 margin: 0 0 20px;
1083 1083 padding: 0;
1084 1084 }
1085 1085
1086 1086 #content div.box div.title h5 {
1087 1087 float: left;
1088 1088 border: none;
1089 1089 color: #fff;
1090 1090 text-transform: uppercase;
1091 1091 margin: 0;
1092 1092 padding: 11px 0 11px 10px;
1093 1093 }
1094 1094
1095 1095 #content div.box div.title .link-white{
1096 1096 color: #FFFFFF;
1097 1097 }
1098 1098
1099 1099 #content div.box div.title .link-white.current{
1100 1100 color: #BFE3FF;
1101 1101 }
1102 1102
1103 1103 #content div.box div.title ul.links li {
1104 1104 list-style: none;
1105 1105 float: left;
1106 1106 margin: 0;
1107 1107 padding: 0;
1108 1108 }
1109 1109
1110 1110 #content div.box div.title ul.links li a {
1111 1111 border-left: 1px solid #316293;
1112 1112 color: #FFFFFF;
1113 1113 display: block;
1114 1114 float: left;
1115 1115 font-size: 13px;
1116 1116 font-weight: 700;
1117 1117 height: 1%;
1118 1118 margin: 0;
1119 1119 padding: 11px 22px 12px;
1120 1120 text-decoration: none;
1121 1121 }
1122 1122
1123 1123 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6
1124 1124 {
1125 1125 clear: both;
1126 1126 overflow: hidden;
1127 1127 border-bottom: 1px solid #DDD;
1128 1128 margin: 10px 20px;
1129 1129 padding: 0 0 15px;
1130 1130 }
1131 1131
1132 1132 #content div.box p {
1133 1133 color: #5f5f5f;
1134 1134 font-size: 12px;
1135 1135 line-height: 150%;
1136 1136 margin: 0 24px 10px;
1137 1137 padding: 0;
1138 1138 }
1139 1139
1140 1140 #content div.box blockquote {
1141 1141 border-left: 4px solid #DDD;
1142 1142 color: #5f5f5f;
1143 1143 font-size: 11px;
1144 1144 line-height: 150%;
1145 1145 margin: 0 34px;
1146 1146 padding: 0 0 0 14px;
1147 1147 }
1148 1148
1149 1149 #content div.box blockquote p {
1150 1150 margin: 10px 0;
1151 1151 padding: 0;
1152 1152 }
1153 1153
1154 1154 #content div.box dl {
1155 1155 margin: 10px 0px;
1156 1156 }
1157 1157
1158 1158 #content div.box dt {
1159 1159 font-size: 12px;
1160 1160 margin: 0;
1161 1161 }
1162 1162
1163 1163 #content div.box dd {
1164 1164 font-size: 12px;
1165 1165 margin: 0;
1166 1166 padding: 8px 0 8px 15px;
1167 1167 }
1168 1168
1169 1169 #content div.box li {
1170 1170 font-size: 12px;
1171 1171 padding: 4px 0;
1172 1172 }
1173 1173
1174 1174 #content div.box ul.disc,#content div.box ul.circle {
1175 1175 margin: 10px 24px 10px 38px;
1176 1176 }
1177 1177
1178 1178 #content div.box ul.square {
1179 1179 margin: 10px 24px 10px 40px;
1180 1180 }
1181 1181
1182 1182 #content div.box img.left {
1183 1183 border: none;
1184 1184 float: left;
1185 1185 margin: 10px 10px 10px 0;
1186 1186 }
1187 1187
1188 1188 #content div.box img.right {
1189 1189 border: none;
1190 1190 float: right;
1191 1191 margin: 10px 0 10px 10px;
1192 1192 }
1193 1193
1194 1194 #content div.box div.messages {
1195 1195 clear: both;
1196 1196 overflow: hidden;
1197 1197 margin: 0 20px;
1198 1198 padding: 0;
1199 1199 }
1200 1200
1201 1201 #content div.box div.message {
1202 1202 clear: both;
1203 1203 overflow: hidden;
1204 1204 margin: 0;
1205 1205 padding: 5px 0;
1206 1206 white-space: pre-wrap;
1207 1207 }
1208 1208 #content div.box div.expand {
1209 1209 width: 110%;
1210 1210 height:14px;
1211 1211 font-size:10px;
1212 1212 text-align:center;
1213 1213 cursor: pointer;
1214 1214 color:#666;
1215 1215
1216 1216 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1217 1217 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1218 1218 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1219 1219 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1220 1220 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1221 1221 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1222 1222
1223 1223 display: none;
1224 1224 }
1225 1225 #content div.box div.expand .expandtext {
1226 1226 background-color: #ffffff;
1227 1227 padding: 2px;
1228 1228 border-radius: 2px;
1229 1229 }
1230 1230
1231 1231 #content div.box div.message a {
1232 1232 font-weight: 400 !important;
1233 1233 }
1234 1234
1235 1235 #content div.box div.message div.image {
1236 1236 float: left;
1237 1237 margin: 9px 0 0 5px;
1238 1238 padding: 6px;
1239 1239 }
1240 1240
1241 1241 #content div.box div.message div.image img {
1242 1242 vertical-align: middle;
1243 1243 margin: 0;
1244 1244 }
1245 1245
1246 1246 #content div.box div.message div.text {
1247 1247 float: left;
1248 1248 margin: 0;
1249 1249 padding: 9px 6px;
1250 1250 }
1251 1251
1252 1252 #content div.box div.message div.dismiss a {
1253 1253 height: 16px;
1254 1254 width: 16px;
1255 1255 display: block;
1256 1256 background: url("../images/icons/cross.png") no-repeat;
1257 1257 margin: 15px 14px 0 0;
1258 1258 padding: 0;
1259 1259 }
1260 1260
1261 1261 #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
1262 1262 {
1263 1263 border: none;
1264 1264 margin: 0;
1265 1265 padding: 0;
1266 1266 }
1267 1267
1268 1268 #content div.box div.message div.text span {
1269 1269 height: 1%;
1270 1270 display: block;
1271 1271 margin: 0;
1272 1272 padding: 5px 0 0;
1273 1273 }
1274 1274
1275 1275 #content div.box div.message-error {
1276 1276 height: 1%;
1277 1277 clear: both;
1278 1278 overflow: hidden;
1279 1279 background: #FBE3E4;
1280 1280 border: 1px solid #FBC2C4;
1281 1281 color: #860006;
1282 1282 }
1283 1283
1284 1284 #content div.box div.message-error h6 {
1285 1285 color: #860006;
1286 1286 }
1287 1287
1288 1288 #content div.box div.message-warning {
1289 1289 height: 1%;
1290 1290 clear: both;
1291 1291 overflow: hidden;
1292 1292 background: #FFF6BF;
1293 1293 border: 1px solid #FFD324;
1294 1294 color: #5f5200;
1295 1295 }
1296 1296
1297 1297 #content div.box div.message-warning h6 {
1298 1298 color: #5f5200;
1299 1299 }
1300 1300
1301 1301 #content div.box div.message-notice {
1302 1302 height: 1%;
1303 1303 clear: both;
1304 1304 overflow: hidden;
1305 1305 background: #8FBDE0;
1306 1306 border: 1px solid #6BACDE;
1307 1307 color: #003863;
1308 1308 }
1309 1309
1310 1310 #content div.box div.message-notice h6 {
1311 1311 color: #003863;
1312 1312 }
1313 1313
1314 1314 #content div.box div.message-success {
1315 1315 height: 1%;
1316 1316 clear: both;
1317 1317 overflow: hidden;
1318 1318 background: #E6EFC2;
1319 1319 border: 1px solid #C6D880;
1320 1320 color: #4e6100;
1321 1321 }
1322 1322
1323 1323 #content div.box div.message-success h6 {
1324 1324 color: #4e6100;
1325 1325 }
1326 1326
1327 1327 #content div.box div.form div.fields div.field {
1328 1328 height: 1%;
1329 1329 border-bottom: 1px solid #DDD;
1330 1330 clear: both;
1331 1331 margin: 0;
1332 1332 padding: 10px 0;
1333 1333 }
1334 1334
1335 1335 #content div.box div.form div.fields div.field-first {
1336 1336 padding: 0 0 10px;
1337 1337 }
1338 1338
1339 1339 #content div.box div.form div.fields div.field-noborder {
1340 1340 border-bottom: 0 !important;
1341 1341 }
1342 1342
1343 1343 #content div.box div.form div.fields div.field span.error-message {
1344 1344 height: 1%;
1345 1345 display: inline-block;
1346 1346 color: red;
1347 1347 margin: 8px 0 0 4px;
1348 1348 padding: 0;
1349 1349 }
1350 1350
1351 1351 #content div.box div.form div.fields div.field span.success {
1352 1352 height: 1%;
1353 1353 display: block;
1354 1354 color: #316309;
1355 1355 margin: 8px 0 0;
1356 1356 padding: 0;
1357 1357 }
1358 1358
1359 1359 #content div.box div.form div.fields div.field div.label {
1360 1360 left: 70px;
1361 1361 width: 155px;
1362 1362 position: absolute;
1363 1363 margin: 0;
1364 1364 padding: 5px 0 0 0px;
1365 1365 }
1366 1366
1367 1367 #content div.box div.form div.fields div.field div.label-summary {
1368 1368 left: 30px;
1369 1369 width: 155px;
1370 1370 position: absolute;
1371 1371 margin: 0;
1372 1372 padding: 0px 0 0 0px;
1373 1373 }
1374 1374
1375 1375 #content div.box-left div.form div.fields div.field div.label,
1376 1376 #content div.box-right div.form div.fields div.field div.label,
1377 1377 #content div.box-left div.form div.fields div.field div.label,
1378 1378 #content div.box-left div.form div.fields div.field div.label-summary,
1379 1379 #content div.box-right div.form div.fields div.field div.label-summary,
1380 1380 #content div.box-left div.form div.fields div.field div.label-summary
1381 1381 {
1382 1382 clear: both;
1383 1383 overflow: hidden;
1384 1384 left: 0;
1385 1385 width: auto;
1386 1386 position: relative;
1387 1387 margin: 0;
1388 1388 padding: 0 0 8px;
1389 1389 }
1390 1390
1391 1391 #content div.box div.form div.fields div.field div.label-select {
1392 1392 padding: 5px 0 0 5px;
1393 1393 }
1394 1394
1395 1395 #content div.box-left div.form div.fields div.field div.label-select,
1396 1396 #content div.box-right div.form div.fields div.field div.label-select
1397 1397 {
1398 1398 padding: 0 0 8px;
1399 1399 }
1400 1400
1401 1401 #content div.box-left div.form div.fields div.field div.label-textarea,
1402 1402 #content div.box-right div.form div.fields div.field div.label-textarea
1403 1403 {
1404 1404 padding: 0 0 8px !important;
1405 1405 }
1406 1406
1407 1407 #content div.box div.form div.fields div.field div.label label,div.label label
1408 1408 {
1409 1409 color: #393939;
1410 1410 font-weight: 700;
1411 1411 }
1412 1412 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1413 1413 {
1414 1414 color: #393939;
1415 1415 font-weight: 700;
1416 1416 }
1417 1417 #content div.box div.form div.fields div.field div.input {
1418 1418 margin: 0 0 0 200px;
1419 1419 }
1420 1420
1421 1421 #content div.box div.form div.fields div.field div.input.summary {
1422 1422 margin: 0 0 0 110px;
1423 1423 }
1424 1424 #content div.box div.form div.fields div.field div.input.summary-short {
1425 1425 margin: 0 0 0 110px;
1426 1426 }
1427 1427 #content div.box div.form div.fields div.field div.file {
1428 1428 margin: 0 0 0 200px;
1429 1429 }
1430 1430
1431 1431 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1432 1432 {
1433 1433 margin: 0 0 0 0px;
1434 1434 }
1435 1435
1436 1436 #content div.box div.form div.fields div.field div.input input,
1437 1437 .reviewer_ac input {
1438 1438 background: #FFF;
1439 1439 border-top: 1px solid #b3b3b3;
1440 1440 border-left: 1px solid #b3b3b3;
1441 1441 border-right: 1px solid #eaeaea;
1442 1442 border-bottom: 1px solid #eaeaea;
1443 1443 color: #000;
1444 1444 font-size: 11px;
1445 1445 margin: 0;
1446 1446 padding: 7px 7px 6px;
1447 1447 }
1448 1448
1449 1449 #content div.box div.form div.fields div.field div.input input#clone_url,
1450 1450 #content div.box div.form div.fields div.field div.input input#clone_url_id
1451 1451 {
1452 1452 font-size: 16px;
1453 1453 padding: 2px;
1454 1454 }
1455 1455
1456 1456 #content div.box div.form div.fields div.field div.file input {
1457 1457 background: none repeat scroll 0 0 #FFFFFF;
1458 1458 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1459 1459 border-style: solid;
1460 1460 border-width: 1px;
1461 1461 color: #000000;
1462 1462 font-size: 11px;
1463 1463 margin: 0;
1464 1464 padding: 7px 7px 6px;
1465 1465 }
1466 1466
1467 1467 input.disabled {
1468 1468 background-color: #F5F5F5 !important;
1469 1469 }
1470 1470 #content div.box div.form div.fields div.field div.input input.small {
1471 1471 width: 30%;
1472 1472 }
1473 1473
1474 1474 #content div.box div.form div.fields div.field div.input input.medium {
1475 1475 width: 55%;
1476 1476 }
1477 1477
1478 1478 #content div.box div.form div.fields div.field div.input input.large {
1479 1479 width: 85%;
1480 1480 }
1481 1481
1482 1482 #content div.box div.form div.fields div.field div.input input.date {
1483 1483 width: 177px;
1484 1484 }
1485 1485
1486 1486 #content div.box div.form div.fields div.field div.input input.button {
1487 1487 background: #D4D0C8;
1488 1488 border-top: 1px solid #FFF;
1489 1489 border-left: 1px solid #FFF;
1490 1490 border-right: 1px solid #404040;
1491 1491 border-bottom: 1px solid #404040;
1492 1492 color: #000;
1493 1493 margin: 0;
1494 1494 padding: 4px 8px;
1495 1495 }
1496 1496
1497 1497 #content div.box div.form div.fields div.field div.textarea {
1498 1498 border-top: 1px solid #b3b3b3;
1499 1499 border-left: 1px solid #b3b3b3;
1500 1500 border-right: 1px solid #eaeaea;
1501 1501 border-bottom: 1px solid #eaeaea;
1502 1502 margin: 0 0 0 200px;
1503 1503 padding: 10px;
1504 1504 }
1505 1505
1506 1506 #content div.box div.form div.fields div.field div.textarea-editor {
1507 1507 border: 1px solid #ddd;
1508 1508 padding: 0;
1509 1509 }
1510 1510
1511 1511 #content div.box div.form div.fields div.field div.textarea textarea {
1512 1512 width: 100%;
1513 1513 height: 220px;
1514 1514 overflow: hidden;
1515 1515 background: #FFF;
1516 1516 color: #000;
1517 1517 font-size: 11px;
1518 1518 outline: none;
1519 1519 border-width: 0;
1520 1520 margin: 0;
1521 1521 padding: 0;
1522 1522 }
1523 1523
1524 1524 #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
1525 1525 {
1526 1526 width: 100%;
1527 1527 height: 100px;
1528 1528 }
1529 1529
1530 1530 #content div.box div.form div.fields div.field div.textarea table {
1531 1531 width: 100%;
1532 1532 border: none;
1533 1533 margin: 0;
1534 1534 padding: 0;
1535 1535 }
1536 1536
1537 1537 #content div.box div.form div.fields div.field div.textarea table td {
1538 1538 background: #DDD;
1539 1539 border: none;
1540 1540 padding: 0;
1541 1541 }
1542 1542
1543 1543 #content div.box div.form div.fields div.field div.textarea table td table
1544 1544 {
1545 1545 width: auto;
1546 1546 border: none;
1547 1547 margin: 0;
1548 1548 padding: 0;
1549 1549 }
1550 1550
1551 1551 #content div.box div.form div.fields div.field div.textarea table td table td
1552 1552 {
1553 1553 font-size: 11px;
1554 1554 padding: 5px 5px 5px 0;
1555 1555 }
1556 1556
1557 1557 #content div.box div.form div.fields div.field input[type=text]:focus,
1558 1558 #content div.box div.form div.fields div.field input[type=password]:focus,
1559 1559 #content div.box div.form div.fields div.field input[type=file]:focus,
1560 1560 #content div.box div.form div.fields div.field textarea:focus,
1561 1561 #content div.box div.form div.fields div.field select:focus,
1562 1562 .reviewer_ac input:focus
1563 1563 {
1564 1564 background: #f6f6f6;
1565 1565 border-color: #666;
1566 1566 }
1567 1567
1568 1568 .reviewer_ac {
1569 1569 padding:10px
1570 1570 }
1571 1571
1572 1572 div.form div.fields div.field div.button {
1573 1573 margin: 0;
1574 1574 padding: 0 0 0 8px;
1575 1575 }
1576 1576 #content div.box table.noborder {
1577 1577 border: 1px solid transparent;
1578 1578 }
1579 1579
1580 1580 #content div.box table {
1581 1581 width: 100%;
1582 1582 border-collapse: separate;
1583 1583 margin: 0;
1584 1584 padding: 0;
1585 1585 border: 1px solid #eee;
1586 1586 -webkit-border-radius: 4px;
1587 1587 -moz-border-radius: 4px;
1588 1588 border-radius: 4px;
1589 1589 }
1590 1590
1591 1591 #content div.box table th {
1592 1592 background: #eee;
1593 1593 border-bottom: 1px solid #ddd;
1594 1594 padding: 5px 0px 5px 5px;
1595 1595 }
1596 1596
1597 1597 #content div.box table th.left {
1598 1598 text-align: left;
1599 1599 }
1600 1600
1601 1601 #content div.box table th.right {
1602 1602 text-align: right;
1603 1603 }
1604 1604
1605 1605 #content div.box table th.center {
1606 1606 text-align: center;
1607 1607 }
1608 1608
1609 1609 #content div.box table th.selected {
1610 1610 vertical-align: middle;
1611 1611 padding: 0;
1612 1612 }
1613 1613
1614 1614 #content div.box table td {
1615 1615 background: #fff;
1616 1616 border-bottom: 1px solid #cdcdcd;
1617 1617 vertical-align: middle;
1618 1618 padding: 5px;
1619 1619 }
1620 1620
1621 1621 #content div.box table tr.selected td {
1622 1622 background: #FFC;
1623 1623 }
1624 1624
1625 1625 #content div.box table td.selected {
1626 1626 width: 3%;
1627 1627 text-align: center;
1628 1628 vertical-align: middle;
1629 1629 padding: 0;
1630 1630 }
1631 1631
1632 1632 #content div.box table td.action {
1633 1633 width: 45%;
1634 1634 text-align: left;
1635 1635 }
1636 1636
1637 1637 #content div.box table td.date {
1638 1638 width: 33%;
1639 1639 text-align: center;
1640 1640 }
1641 1641
1642 1642 #content div.box div.action {
1643 1643 float: right;
1644 1644 background: #FFF;
1645 1645 text-align: right;
1646 1646 margin: 10px 0 0;
1647 1647 padding: 0;
1648 1648 }
1649 1649
1650 1650 #content div.box div.action select {
1651 1651 font-size: 11px;
1652 1652 margin: 0;
1653 1653 }
1654 1654
1655 1655 #content div.box div.action .ui-selectmenu {
1656 1656 margin: 0;
1657 1657 padding: 0;
1658 1658 }
1659 1659
1660 1660 #content div.box div.pagination {
1661 1661 height: 1%;
1662 1662 clear: both;
1663 1663 overflow: hidden;
1664 1664 margin: 10px 0 0;
1665 1665 padding: 0;
1666 1666 }
1667 1667
1668 1668 #content div.box div.pagination ul.pager {
1669 1669 float: right;
1670 1670 text-align: right;
1671 1671 margin: 0;
1672 1672 padding: 0;
1673 1673 }
1674 1674
1675 1675 #content div.box div.pagination ul.pager li {
1676 1676 height: 1%;
1677 1677 float: left;
1678 1678 list-style: none;
1679 1679 background: #ebebeb url("../images/pager.png") repeat-x;
1680 1680 border-top: 1px solid #dedede;
1681 1681 border-left: 1px solid #cfcfcf;
1682 1682 border-right: 1px solid #c4c4c4;
1683 1683 border-bottom: 1px solid #c4c4c4;
1684 1684 color: #4A4A4A;
1685 1685 font-weight: 700;
1686 1686 margin: 0 0 0 4px;
1687 1687 padding: 0;
1688 1688 }
1689 1689
1690 1690 #content div.box div.pagination ul.pager li.separator {
1691 1691 padding: 6px;
1692 1692 }
1693 1693
1694 1694 #content div.box div.pagination ul.pager li.current {
1695 1695 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1696 1696 border-top: 1px solid #ccc;
1697 1697 border-left: 1px solid #bebebe;
1698 1698 border-right: 1px solid #b1b1b1;
1699 1699 border-bottom: 1px solid #afafaf;
1700 1700 color: #515151;
1701 1701 padding: 6px;
1702 1702 }
1703 1703
1704 1704 #content div.box div.pagination ul.pager li a {
1705 1705 height: 1%;
1706 1706 display: block;
1707 1707 float: left;
1708 1708 color: #515151;
1709 1709 text-decoration: none;
1710 1710 margin: 0;
1711 1711 padding: 6px;
1712 1712 }
1713 1713
1714 1714 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1715 1715 {
1716 1716 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1717 1717 border-top: 1px solid #ccc;
1718 1718 border-left: 1px solid #bebebe;
1719 1719 border-right: 1px solid #b1b1b1;
1720 1720 border-bottom: 1px solid #afafaf;
1721 1721 margin: -1px;
1722 1722 }
1723 1723
1724 1724 #content div.box div.pagination-wh {
1725 1725 height: 1%;
1726 1726 clear: both;
1727 1727 overflow: hidden;
1728 1728 text-align: right;
1729 1729 margin: 10px 0 0;
1730 1730 padding: 0;
1731 1731 }
1732 1732
1733 1733 #content div.box div.pagination-right {
1734 1734 float: right;
1735 1735 }
1736 1736
1737 1737 #content div.box div.pagination-wh a,
1738 1738 #content div.box div.pagination-wh span.pager_dotdot,
1739 1739 #content div.box div.pagination-wh span.yui-pg-previous,
1740 1740 #content div.box div.pagination-wh span.yui-pg-last,
1741 1741 #content div.box div.pagination-wh span.yui-pg-next,
1742 1742 #content div.box div.pagination-wh span.yui-pg-first
1743 1743 {
1744 1744 height: 1%;
1745 1745 float: left;
1746 1746 background: #ebebeb url("../images/pager.png") repeat-x;
1747 1747 border-top: 1px solid #dedede;
1748 1748 border-left: 1px solid #cfcfcf;
1749 1749 border-right: 1px solid #c4c4c4;
1750 1750 border-bottom: 1px solid #c4c4c4;
1751 1751 color: #4A4A4A;
1752 1752 font-weight: 700;
1753 1753 margin: 0 0 0 4px;
1754 1754 padding: 6px;
1755 1755 }
1756 1756
1757 1757 #content div.box div.pagination-wh span.pager_curpage {
1758 1758 height: 1%;
1759 1759 float: left;
1760 1760 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1761 1761 border-top: 1px solid #ccc;
1762 1762 border-left: 1px solid #bebebe;
1763 1763 border-right: 1px solid #b1b1b1;
1764 1764 border-bottom: 1px solid #afafaf;
1765 1765 color: #515151;
1766 1766 font-weight: 700;
1767 1767 margin: 0 0 0 4px;
1768 1768 padding: 6px;
1769 1769 }
1770 1770
1771 1771 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1772 1772 {
1773 1773 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1774 1774 border-top: 1px solid #ccc;
1775 1775 border-left: 1px solid #bebebe;
1776 1776 border-right: 1px solid #b1b1b1;
1777 1777 border-bottom: 1px solid #afafaf;
1778 1778 text-decoration: none;
1779 1779 }
1780 1780
1781 1781 #content div.box div.traffic div.legend {
1782 1782 clear: both;
1783 1783 overflow: hidden;
1784 1784 border-bottom: 1px solid #ddd;
1785 1785 margin: 0 0 10px;
1786 1786 padding: 0 0 10px;
1787 1787 }
1788 1788
1789 1789 #content div.box div.traffic div.legend h6 {
1790 1790 float: left;
1791 1791 border: none;
1792 1792 margin: 0;
1793 1793 padding: 0;
1794 1794 }
1795 1795
1796 1796 #content div.box div.traffic div.legend li {
1797 1797 list-style: none;
1798 1798 float: left;
1799 1799 font-size: 11px;
1800 1800 margin: 0;
1801 1801 padding: 0 8px 0 4px;
1802 1802 }
1803 1803
1804 1804 #content div.box div.traffic div.legend li.visits {
1805 1805 border-left: 12px solid #edc240;
1806 1806 }
1807 1807
1808 1808 #content div.box div.traffic div.legend li.pageviews {
1809 1809 border-left: 12px solid #afd8f8;
1810 1810 }
1811 1811
1812 1812 #content div.box div.traffic table {
1813 1813 width: auto;
1814 1814 }
1815 1815
1816 1816 #content div.box div.traffic table td {
1817 1817 background: transparent;
1818 1818 border: none;
1819 1819 padding: 2px 3px 3px;
1820 1820 }
1821 1821
1822 1822 #content div.box div.traffic table td.legendLabel {
1823 1823 padding: 0 3px 2px;
1824 1824 }
1825 1825
1826 1826 #summary {
1827 1827
1828 1828 }
1829 1829
1830 #summary .metatag {
1831 display: inline-block;
1832 padding: 3px 5px;
1833 margin-bottom: 3px;
1834 margin-right: 1px;
1835 border-radius: 5px;
1836 }
1837
1838 #content div.box #summary p {
1839 margin-bottom: -5px;
1840 width: 600px;
1841 white-space: pre-wrap;
1842 }
1843
1844 #content div.box #summary p:last-child {
1845 margin-bottom: 9px;
1846 }
1847
1848 #content div.box #summary p:first-of-type {
1849 margin-top: 9px;
1850 }
1851
1852 .metatag {
1853 display: inline-block;
1854 margin-right: 1px;
1855 -webkit-border-radius: 4px 4px 4px 4px;
1856 -khtml-border-radius: 4px 4px 4px 4px;
1857 -moz-border-radius: 4px 4px 4px 4px;
1858 border-radius: 4px 4px 4px 4px;
1859
1860 border: solid 1px #9CF;
1861 padding: 2px 3px 2px 3px !important;
1862 background-color: #DEF;
1863 }
1864
1865 .metatag[tag="dead"] {
1866 background-color: #E44;
1867 }
1868
1869 .metatag[tag="stale"] {
1870 background-color: #EA4;
1871 }
1872
1873 .metatag[tag="featured"] {
1874 background-color: #AEA;
1875 }
1876
1877 .metatag[tag="requires"] {
1878 background-color: #9CF;
1879 }
1880
1881 .metatag[tag="recommends"] {
1882 background-color: #BDF;
1883 }
1884
1885 .metatag[tag="lang"] {
1886 background-color: #FAF474;
1887 }
1888
1889 .metatag[tag="license"] {
1890 border: solid 1px #9CF;
1891 background-color: #DEF;
1892 target-new: tab !important;
1893 }
1894 .metatag[tag="see"] {
1895 border: solid 1px #CBD;
1896 background-color: #EDF;
1897 }
1898
1899 a.metatag[tag="license"]:hover {
1900 background-color: #003367;
1901 color: #FFF;
1902 text-decoration: none;
1903 }
1904
1830 1905 #summary .desc {
1831 1906 white-space: pre;
1832 1907 width: 100%;
1833 1908 }
1834 1909
1835 1910 #summary .repo_name {
1836 1911 font-size: 1.6em;
1837 1912 font-weight: bold;
1838 1913 vertical-align: baseline;
1839 1914 clear: right
1840 1915 }
1841 1916
1842 1917 #footer {
1843 1918 clear: both;
1844 1919 overflow: hidden;
1845 1920 text-align: right;
1846 1921 margin: 0;
1847 1922 padding: 0 10px 4px;
1848 1923 margin: -10px 0 0;
1849 1924 }
1850 1925
1851 1926 #footer div#footer-inner {
1852 1927 background-color: #003B76;
1853 1928 background-repeat : repeat-x;
1854 1929 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1855 1930 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1856 1931 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1857 1932 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1858 1933 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1859 1934 background-image : -o-linear-gradient( top, #003b76, #00376e));
1860 1935 background-image : linear-gradient( top, #003b76, #00376e);
1861 1936 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1862 1937 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1863 1938 -webkit-border-radius: 4px 4px 4px 4px;
1864 1939 -khtml-border-radius: 4px 4px 4px 4px;
1865 1940 -moz-border-radius: 4px 4px 4px 4px;
1866 1941 border-radius: 4px 4px 4px 4px;
1867 1942 }
1868 1943
1869 1944 #footer div#footer-inner p {
1870 1945 padding: 15px 25px 15px 0;
1871 1946 color: #FFF;
1872 1947 font-weight: 700;
1873 1948 }
1874 1949
1875 1950 #footer div#footer-inner .footer-link {
1876 1951 float: left;
1877 1952 padding-left: 10px;
1878 1953 }
1879 1954
1880 1955 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
1881 1956 {
1882 1957 color: #FFF;
1883 1958 }
1884 1959
1885 1960 #login div.title {
1886 1961 width: 420px;
1887 1962 clear: both;
1888 1963 overflow: hidden;
1889 1964 position: relative;
1890 1965 background-color: #003B76;
1891 1966 background-repeat : repeat-x;
1892 1967 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1893 1968 background-image : -moz-linear-gradient( top, #003b76, #00376e);
1894 1969 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1895 1970 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1896 1971 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1897 1972 background-image : -o-linear-gradient( top, #003b76, #00376e));
1898 1973 background-image : linear-gradient( top, #003b76, #00376e);
1899 1974 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1900 1975 margin: 0 auto;
1901 1976 padding: 0;
1902 1977 }
1903 1978
1904 1979 #login div.inner {
1905 1980 width: 380px;
1906 1981 background: #FFF url("../images/login.png") no-repeat top left;
1907 1982 border-top: none;
1908 1983 border-bottom: none;
1909 1984 margin: 0 auto;
1910 1985 padding: 20px;
1911 1986 }
1912 1987
1913 1988 #login div.form div.fields div.field div.label {
1914 1989 width: 173px;
1915 1990 float: left;
1916 1991 text-align: right;
1917 1992 margin: 2px 10px 0 0;
1918 1993 padding: 5px 0 0 5px;
1919 1994 }
1920 1995
1921 1996 #login div.form div.fields div.field div.input input {
1922 1997 width: 176px;
1923 1998 background: #FFF;
1924 1999 border-top: 1px solid #b3b3b3;
1925 2000 border-left: 1px solid #b3b3b3;
1926 2001 border-right: 1px solid #eaeaea;
1927 2002 border-bottom: 1px solid #eaeaea;
1928 2003 color: #000;
1929 2004 font-size: 11px;
1930 2005 margin: 0;
1931 2006 padding: 7px 7px 6px;
1932 2007 }
1933 2008
1934 2009 #login div.form div.fields div.buttons {
1935 2010 clear: both;
1936 2011 overflow: hidden;
1937 2012 border-top: 1px solid #DDD;
1938 2013 text-align: right;
1939 2014 margin: 0;
1940 2015 padding: 10px 0 0;
1941 2016 }
1942 2017
1943 2018 #login div.form div.links {
1944 2019 clear: both;
1945 2020 overflow: hidden;
1946 2021 margin: 10px 0 0;
1947 2022 padding: 0 0 2px;
1948 2023 }
1949 2024
1950 2025 .user-menu{
1951 2026 margin: 0px !important;
1952 2027 float: left;
1953 2028 }
1954 2029
1955 2030 .user-menu .container{
1956 2031 padding:0px 4px 0px 4px;
1957 2032 margin: 0px 0px 0px 0px;
1958 2033 }
1959 2034
1960 2035 .user-menu .gravatar{
1961 2036 margin: 0px 0px 0px 0px;
1962 2037 cursor: pointer;
1963 2038 }
1964 2039 .user-menu .gravatar.enabled{
1965 2040 background-color: #FDF784 !important;
1966 2041 }
1967 2042 .user-menu .gravatar:hover{
1968 2043 background-color: #FDF784 !important;
1969 2044 }
1970 2045 #quick_login{
1971 2046 min-height: 80px;
1972 2047 margin: 37px 0 0 -251px;
1973 2048 padding: 4px;
1974 2049 position: absolute;
1975 2050 width: 278px;
1976 2051 background-color: #003B76;
1977 2052 background-repeat: repeat-x;
1978 2053 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1979 2054 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1980 2055 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1981 2056 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1982 2057 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1983 2058 background-image: -o-linear-gradient(top, #003b76, #00376e);
1984 2059 background-image: linear-gradient(top, #003b76, #00376e);
1985 2060 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1986 2061
1987 2062 z-index: 999;
1988 2063 -webkit-border-radius: 0px 0px 4px 4px;
1989 2064 -khtml-border-radius: 0px 0px 4px 4px;
1990 2065 -moz-border-radius: 0px 0px 4px 4px;
1991 2066 border-radius: 0px 0px 4px 4px;
1992 2067 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1993 2068 }
1994 2069 #quick_login h4{
1995 2070 color: #fff;
1996 2071 padding: 5px 0px 5px 14px;
1997 2072 }
1998 2073
1999 2074 #quick_login .password_forgoten {
2000 2075 padding-right: 10px;
2001 2076 padding-top: 0px;
2002 2077 text-align: left;
2003 2078 }
2004 2079
2005 2080 #quick_login .password_forgoten a {
2006 2081 font-size: 10px;
2007 2082 color: #fff;
2008 2083 }
2009 2084
2010 2085 #quick_login .register {
2011 2086 padding-right: 10px;
2012 2087 padding-top: 5px;
2013 2088 text-align: left;
2014 2089 }
2015 2090
2016 2091 #quick_login .register a {
2017 2092 font-size: 10px;
2018 2093 color: #fff;
2019 2094 }
2020 2095
2021 2096 #quick_login .submit {
2022 2097 margin: -20px 0 0 0px;
2023 2098 position: absolute;
2024 2099 right: 15px;
2025 2100 }
2026 2101
2027 2102 #quick_login .links_left{
2028 2103 float: left;
2029 2104 }
2030 2105 #quick_login .links_right{
2031 2106 float: right;
2032 2107 }
2033 2108 #quick_login .full_name{
2034 2109 color: #FFFFFF;
2035 2110 font-weight: bold;
2036 2111 padding: 3px;
2037 2112 }
2038 2113 #quick_login .big_gravatar{
2039 2114 padding:4px 0px 0px 6px;
2040 2115 }
2041 2116 #quick_login .inbox{
2042 2117 padding:4px 0px 0px 6px;
2043 2118 color: #FFFFFF;
2044 2119 font-weight: bold;
2045 2120 }
2046 2121 #quick_login .inbox a{
2047 2122 color: #FFFFFF;
2048 2123 }
2049 2124 #quick_login .email,#quick_login .email a{
2050 2125 color: #FFFFFF;
2051 2126 padding: 3px;
2052 2127
2053 2128 }
2054 2129 #quick_login .links .logout{
2055 2130
2056 2131 }
2057 2132
2058 2133 #quick_login div.form div.fields {
2059 2134 padding-top: 2px;
2060 2135 padding-left: 10px;
2061 2136 }
2062 2137
2063 2138 #quick_login div.form div.fields div.field {
2064 2139 padding: 5px;
2065 2140 }
2066 2141
2067 2142 #quick_login div.form div.fields div.field div.label label {
2068 2143 color: #fff;
2069 2144 padding-bottom: 3px;
2070 2145 }
2071 2146
2072 2147 #quick_login div.form div.fields div.field div.input input {
2073 2148 width: 236px;
2074 2149 background: #FFF;
2075 2150 border-top: 1px solid #b3b3b3;
2076 2151 border-left: 1px solid #b3b3b3;
2077 2152 border-right: 1px solid #eaeaea;
2078 2153 border-bottom: 1px solid #eaeaea;
2079 2154 color: #000;
2080 2155 font-size: 11px;
2081 2156 margin: 0;
2082 2157 padding: 5px 7px 4px;
2083 2158 }
2084 2159
2085 2160 #quick_login div.form div.fields div.buttons {
2086 2161 clear: both;
2087 2162 overflow: hidden;
2088 2163 text-align: right;
2089 2164 margin: 0;
2090 2165 padding: 5px 14px 0px 5px;
2091 2166 }
2092 2167
2093 2168 #quick_login div.form div.links {
2094 2169 clear: both;
2095 2170 overflow: hidden;
2096 2171 margin: 10px 0 0;
2097 2172 padding: 0 0 2px;
2098 2173 }
2099 2174
2100 2175 #quick_login ol.links{
2101 2176 display: block;
2102 2177 font-weight: bold;
2103 2178 list-style: none outside none;
2104 2179 text-align: right;
2105 2180 }
2106 2181 #quick_login ol.links li{
2107 2182 line-height: 27px;
2108 2183 margin: 0;
2109 2184 padding: 0;
2110 2185 color: #fff;
2111 2186 display: block;
2112 2187 float:none !important;
2113 2188 }
2114 2189
2115 2190 #quick_login ol.links li a{
2116 2191 color: #fff;
2117 2192 display: block;
2118 2193 padding: 2px;
2119 2194 }
2120 2195 #quick_login ol.links li a:HOVER{
2121 2196 background-color: inherit !important;
2122 2197 }
2123 2198
2124 2199 #register div.title {
2125 2200 clear: both;
2126 2201 overflow: hidden;
2127 2202 position: relative;
2128 2203 background-color: #003B76;
2129 2204 background-repeat: repeat-x;
2130 2205 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2131 2206 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2132 2207 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2133 2208 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2134 2209 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2135 2210 background-image: -o-linear-gradient(top, #003b76, #00376e);
2136 2211 background-image: linear-gradient(top, #003b76, #00376e);
2137 2212 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2138 2213 endColorstr='#00376e', GradientType=0 );
2139 2214 margin: 0 auto;
2140 2215 padding: 0;
2141 2216 }
2142 2217
2143 2218 #register div.inner {
2144 2219 background: #FFF;
2145 2220 border-top: none;
2146 2221 border-bottom: none;
2147 2222 margin: 0 auto;
2148 2223 padding: 20px;
2149 2224 }
2150 2225
2151 2226 #register div.form div.fields div.field div.label {
2152 2227 width: 135px;
2153 2228 float: left;
2154 2229 text-align: right;
2155 2230 margin: 2px 10px 0 0;
2156 2231 padding: 5px 0 0 5px;
2157 2232 }
2158 2233
2159 2234 #register div.form div.fields div.field div.input input {
2160 2235 width: 300px;
2161 2236 background: #FFF;
2162 2237 border-top: 1px solid #b3b3b3;
2163 2238 border-left: 1px solid #b3b3b3;
2164 2239 border-right: 1px solid #eaeaea;
2165 2240 border-bottom: 1px solid #eaeaea;
2166 2241 color: #000;
2167 2242 font-size: 11px;
2168 2243 margin: 0;
2169 2244 padding: 7px 7px 6px;
2170 2245 }
2171 2246
2172 2247 #register div.form div.fields div.buttons {
2173 2248 clear: both;
2174 2249 overflow: hidden;
2175 2250 border-top: 1px solid #DDD;
2176 2251 text-align: left;
2177 2252 margin: 0;
2178 2253 padding: 10px 0 0 150px;
2179 2254 }
2180 2255
2181 2256 #register div.form div.activation_msg {
2182 2257 padding-top: 4px;
2183 2258 padding-bottom: 4px;
2184 2259 }
2185 2260
2186 2261 #journal .journal_day {
2187 2262 font-size: 20px;
2188 2263 padding: 10px 0px;
2189 2264 border-bottom: 2px solid #DDD;
2190 2265 margin-left: 10px;
2191 2266 margin-right: 10px;
2192 2267 }
2193 2268
2194 2269 #journal .journal_container {
2195 2270 padding: 5px;
2196 2271 clear: both;
2197 2272 margin: 0px 5px 0px 10px;
2198 2273 }
2199 2274
2200 2275 #journal .journal_action_container {
2201 2276 padding-left: 38px;
2202 2277 }
2203 2278
2204 2279 #journal .journal_user {
2205 2280 color: #747474;
2206 2281 font-size: 14px;
2207 2282 font-weight: bold;
2208 2283 height: 30px;
2209 2284 }
2210 2285
2211 2286 #journal .journal_icon {
2212 2287 clear: both;
2213 2288 float: left;
2214 2289 padding-right: 4px;
2215 2290 padding-top: 3px;
2216 2291 }
2217 2292
2218 2293 #journal .journal_action {
2219 2294 padding-top: 4px;
2220 2295 min-height: 2px;
2221 2296 float: left
2222 2297 }
2223 2298
2224 2299 #journal .journal_action_params {
2225 2300 clear: left;
2226 2301 padding-left: 22px;
2227 2302 }
2228 2303
2229 2304 #journal .journal_repo {
2230 2305 float: left;
2231 2306 margin-left: 6px;
2232 2307 padding-top: 3px;
2233 2308 }
2234 2309
2235 2310 #journal .date {
2236 2311 clear: both;
2237 2312 color: #777777;
2238 2313 font-size: 11px;
2239 2314 padding-left: 22px;
2240 2315 }
2241 2316
2242 2317 #journal .journal_repo .journal_repo_name {
2243 2318 font-weight: bold;
2244 2319 font-size: 1.1em;
2245 2320 }
2246 2321
2247 2322 #journal .compare_view {
2248 2323 padding: 5px 0px 5px 0px;
2249 2324 width: 95px;
2250 2325 }
2251 2326
2252 2327 .journal_highlight {
2253 2328 font-weight: bold;
2254 2329 padding: 0 2px;
2255 2330 vertical-align: bottom;
2256 2331 }
2257 2332
2258 2333 .trending_language_tbl,.trending_language_tbl td {
2259 2334 border: 0 !important;
2260 2335 margin: 0 !important;
2261 2336 padding: 0 !important;
2262 2337 }
2263 2338
2264 2339 .trending_language_tbl,.trending_language_tbl tr {
2265 2340 border-spacing: 1px;
2266 2341 }
2267 2342
2268 2343 .trending_language {
2269 2344 background-color: #003367;
2270 2345 color: #FFF;
2271 2346 display: block;
2272 2347 min-width: 20px;
2273 2348 text-decoration: none;
2274 2349 height: 12px;
2275 2350 margin-bottom: 0px;
2276 2351 margin-left: 5px;
2277 2352 white-space: pre;
2278 2353 padding: 3px;
2279 2354 }
2280 2355
2281 2356 h3.files_location {
2282 2357 font-size: 1.8em;
2283 2358 font-weight: 700;
2284 2359 border-bottom: none !important;
2285 2360 margin: 10px 0 !important;
2286 2361 }
2287 2362
2288 2363 #files_data dl dt {
2289 2364 float: left;
2290 2365 width: 60px;
2291 2366 margin: 0 !important;
2292 2367 padding: 5px;
2293 2368 }
2294 2369
2295 2370 #files_data dl dd {
2296 2371 margin: 0 !important;
2297 2372 padding: 5px !important;
2298 2373 }
2299 2374
2300 2375 .file_history{
2301 2376 padding-top:10px;
2302 2377 font-size:16px;
2303 2378 }
2304 2379 .file_author{
2305 2380 float: left;
2306 2381 }
2307 2382
2308 2383 .file_author .item{
2309 2384 float:left;
2310 2385 padding:5px;
2311 2386 color: #888;
2312 2387 }
2313 2388
2314 2389 .tablerow0 {
2315 2390 background-color: #F8F8F8;
2316 2391 }
2317 2392
2318 2393 .tablerow1 {
2319 2394 background-color: #FFFFFF;
2320 2395 }
2321 2396
2322 2397 .changeset_id {
2323 2398 font-family: monospace;
2324 2399 color: #666666;
2325 2400 }
2326 2401
2327 2402 .changeset_hash {
2328 2403 color: #000000;
2329 2404 }
2330 2405
2331 2406 #changeset_content {
2332 2407 border-left: 1px solid #CCC;
2333 2408 border-right: 1px solid #CCC;
2334 2409 border-bottom: 1px solid #CCC;
2335 2410 padding: 5px;
2336 2411 }
2337 2412
2338 2413 #changeset_compare_view_content {
2339 2414 border: 1px solid #CCC;
2340 2415 padding: 5px;
2341 2416 }
2342 2417
2343 2418 #changeset_content .container {
2344 2419 min-height: 100px;
2345 2420 font-size: 1.2em;
2346 2421 overflow: hidden;
2347 2422 }
2348 2423
2349 2424 #changeset_compare_view_content .compare_view_commits {
2350 2425 width: auto !important;
2351 2426 }
2352 2427
2353 2428 #changeset_compare_view_content .compare_view_commits td {
2354 2429 padding: 0px 0px 0px 12px !important;
2355 2430 }
2356 2431
2357 2432 #changeset_content .container .right {
2358 2433 float: right;
2359 2434 width: 20%;
2360 2435 text-align: right;
2361 2436 }
2362 2437
2363 2438 #changeset_content .container .left .message {
2364 2439 white-space: pre-wrap;
2365 2440 }
2366 2441 #changeset_content .container .left .message a:hover {
2367 2442 text-decoration: none;
2368 2443 }
2369 2444 .cs_files .cur_cs {
2370 2445 margin: 10px 2px;
2371 2446 font-weight: bold;
2372 2447 }
2373 2448
2374 2449 .cs_files .node {
2375 2450 float: left;
2376 2451 }
2377 2452
2378 2453 .cs_files .changes {
2379 2454 float: right;
2380 2455 color:#003367;
2381 2456
2382 2457 }
2383 2458
2384 2459 .cs_files .changes .added {
2385 2460 background-color: #BBFFBB;
2386 2461 float: left;
2387 2462 text-align: center;
2388 2463 font-size: 9px;
2389 2464 padding: 2px 0px 2px 0px;
2390 2465 }
2391 2466
2392 2467 .cs_files .changes .deleted {
2393 2468 background-color: #FF8888;
2394 2469 float: left;
2395 2470 text-align: center;
2396 2471 font-size: 9px;
2397 2472 padding: 2px 0px 2px 0px;
2398 2473 }
2399 2474
2400 2475 .cs_files .cs_added,.cs_files .cs_A {
2401 2476 background: url("../images/icons/page_white_add.png") no-repeat scroll
2402 2477 3px;
2403 2478 height: 16px;
2404 2479 padding-left: 20px;
2405 2480 margin-top: 7px;
2406 2481 text-align: left;
2407 2482 }
2408 2483
2409 2484 .cs_files .cs_changed,.cs_files .cs_M {
2410 2485 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2411 2486 3px;
2412 2487 height: 16px;
2413 2488 padding-left: 20px;
2414 2489 margin-top: 7px;
2415 2490 text-align: left;
2416 2491 }
2417 2492
2418 2493 .cs_files .cs_removed,.cs_files .cs_D {
2419 2494 background: url("../images/icons/page_white_delete.png") no-repeat
2420 2495 scroll 3px;
2421 2496 height: 16px;
2422 2497 padding-left: 20px;
2423 2498 margin-top: 7px;
2424 2499 text-align: left;
2425 2500 }
2426 2501
2427 2502 #graph {
2428 2503 overflow: hidden;
2429 2504 }
2430 2505
2431 2506 #graph_nodes {
2432 2507 float: left;
2433 2508 margin-right: -6px;
2434 2509 margin-top: 0px;
2435 2510 }
2436 2511
2437 2512 #graph_content {
2438 2513 width: 80%;
2439 2514 float: left;
2440 2515 }
2441 2516
2442 2517 #graph_content .container_header {
2443 2518 border-bottom: 1px solid #DDD;
2444 2519 padding: 10px;
2445 2520 height: 25px;
2446 2521 }
2447 2522
2448 2523 #graph_content #rev_range_container {
2449 2524 padding: 7px 20px;
2450 2525 float: left;
2451 2526 }
2452 2527
2453 2528 #graph_content .container {
2454 2529 border-bottom: 1px solid #DDD;
2455 2530 height: 56px;
2456 2531 overflow: hidden;
2457 2532 }
2458 2533
2459 2534 #graph_content .container .right {
2460 2535 float: right;
2461 2536 width: 23%;
2462 2537 text-align: right;
2463 2538 }
2464 2539
2465 2540 #graph_content .container .left {
2466 2541 float: left;
2467 2542 width: 25%;
2468 2543 padding-left: 5px;
2469 2544 }
2470 2545
2471 2546 #graph_content .container .mid {
2472 2547 float: left;
2473 2548 width: 49%;
2474 2549 }
2475 2550
2476 2551
2477 2552 #graph_content .container .left .date {
2478 2553 color: #666;
2479 2554 padding-left: 22px;
2480 2555 font-size: 10px;
2481 2556 }
2482 2557
2483 2558 #graph_content .container .left .author {
2484 2559 height: 22px;
2485 2560 }
2486 2561
2487 2562 #graph_content .container .left .author .user {
2488 2563 color: #444444;
2489 2564 float: left;
2490 2565 margin-left: -4px;
2491 2566 margin-top: 4px;
2492 2567 }
2493 2568
2494 2569 #graph_content .container .mid .message {
2495 2570 white-space: pre-wrap;
2496 2571 }
2497 2572
2498 2573 #graph_content .container .mid .message a:hover{
2499 2574 text-decoration: none;
2500 2575 }
2501 2576 #content #graph_content .message .revision-link,
2502 2577 #changeset_content .container .message .revision-link
2503 2578 {
2504 2579 color:#3F6F9F;
2505 2580 font-weight: bold !important;
2506 2581 }
2507 2582
2508 2583 #content #graph_content .message .issue-tracker-link,
2509 2584 #changeset_content .container .message .issue-tracker-link{
2510 2585 color:#3F6F9F;
2511 2586 font-weight: bold !important;
2512 2587 }
2513 2588
2514 2589 .changeset-status-container{
2515 2590 padding-right: 5px;
2516 2591 margin-top:1px;
2517 2592 float:right;
2518 2593 height:14px;
2519 2594 }
2520 2595 .code-header .changeset-status-container{
2521 2596 float:left;
2522 2597 padding:2px 0px 0px 2px;
2523 2598 }
2524 2599 .changeset-status-container .changeset-status-lbl{
2525 2600 color: rgb(136, 136, 136);
2526 2601 float: left;
2527 2602 padding: 3px 4px 0px 0px
2528 2603 }
2529 2604 .code-header .changeset-status-container .changeset-status-lbl{
2530 2605 float: left;
2531 2606 padding: 0px 4px 0px 0px;
2532 2607 }
2533 2608 .changeset-status-container .changeset-status-ico{
2534 2609 float: left;
2535 2610 }
2536 2611 .code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
2537 2612 float: left;
2538 2613 }
2539 2614 .right .comments-container{
2540 2615 padding-right: 5px;
2541 2616 margin-top:1px;
2542 2617 float:right;
2543 2618 height:14px;
2544 2619 }
2545 2620
2546 2621 .right .comments-cnt{
2547 2622 float: left;
2548 2623 color: rgb(136, 136, 136);
2549 2624 padding-right: 2px;
2550 2625 }
2551 2626
2552 2627 .right .changes{
2553 2628 clear: both;
2554 2629 }
2555 2630
2556 2631 .right .changes .changed_total {
2557 2632 display: block;
2558 2633 float: right;
2559 2634 text-align: center;
2560 2635 min-width: 45px;
2561 2636 cursor: pointer;
2562 2637 color: #444444;
2563 2638 background: #FEA;
2564 2639 -webkit-border-radius: 0px 0px 0px 6px;
2565 2640 -moz-border-radius: 0px 0px 0px 6px;
2566 2641 border-radius: 0px 0px 0px 6px;
2567 2642 padding: 1px;
2568 2643 }
2569 2644
2570 2645 .right .changes .added,.changed,.removed {
2571 2646 display: block;
2572 2647 padding: 1px;
2573 2648 color: #444444;
2574 2649 float: right;
2575 2650 text-align: center;
2576 2651 min-width: 15px;
2577 2652 }
2578 2653
2579 2654 .right .changes .added {
2580 2655 background: #CFC;
2581 2656 }
2582 2657
2583 2658 .right .changes .changed {
2584 2659 background: #FEA;
2585 2660 }
2586 2661
2587 2662 .right .changes .removed {
2588 2663 background: #FAA;
2589 2664 }
2590 2665
2591 2666 .right .merge {
2592 2667 padding: 1px 3px 1px 3px;
2593 2668 background-color: #fca062;
2594 2669 font-size: 10px;
2595 2670 font-weight: bold;
2596 2671 color: #ffffff;
2597 2672 text-transform: uppercase;
2598 2673 white-space: nowrap;
2599 2674 -webkit-border-radius: 3px;
2600 2675 -moz-border-radius: 3px;
2601 2676 border-radius: 3px;
2602 2677 margin-right: 2px;
2603 2678 }
2604 2679
2605 2680 .right .parent {
2606 2681 color: #666666;
2607 2682 clear:both;
2608 2683 }
2609 2684 .right .logtags{
2610 2685 padding: 2px 2px 2px 2px;
2611 2686 }
2612 2687 .right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
2613 2688 margin: 0px 2px;
2614 2689 }
2615 2690
2616 2691 .right .logtags .branchtag,.logtags .branchtag {
2617 2692 padding: 1px 3px 1px 3px;
2618 2693 background-color: #bfbfbf;
2619 2694 font-size: 10px;
2620 2695 font-weight: bold;
2621 2696 color: #ffffff;
2622 2697 text-transform: uppercase;
2623 2698 white-space: nowrap;
2624 2699 -webkit-border-radius: 3px;
2625 2700 -moz-border-radius: 3px;
2626 2701 border-radius: 3px;
2627 2702 }
2628 2703 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2629 2704 color: #ffffff;
2630 2705 }
2631 2706 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2632 2707 text-decoration: none;
2633 2708 color: #ffffff;
2634 2709 }
2635 2710 .right .logtags .tagtag,.logtags .tagtag {
2636 2711 padding: 1px 3px 1px 3px;
2637 2712 background-color: #62cffc;
2638 2713 font-size: 10px;
2639 2714 font-weight: bold;
2640 2715 color: #ffffff;
2641 2716 text-transform: uppercase;
2642 2717 white-space: nowrap;
2643 2718 -webkit-border-radius: 3px;
2644 2719 -moz-border-radius: 3px;
2645 2720 border-radius: 3px;
2646 2721 }
2647 2722 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2648 2723 color: #ffffff;
2649 2724 }
2650 2725 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2651 2726 text-decoration: none;
2652 2727 color: #ffffff;
2653 2728 }
2654 2729 .right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
2655 2730 padding: 1px 3px 1px 3px;
2656 2731 background-color: #46A546;
2657 2732 font-size: 10px;
2658 2733 font-weight: bold;
2659 2734 color: #ffffff;
2660 2735 text-transform: uppercase;
2661 2736 white-space: nowrap;
2662 2737 -webkit-border-radius: 3px;
2663 2738 -moz-border-radius: 3px;
2664 2739 border-radius: 3px;
2665 2740 }
2666 2741 .right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
2667 2742 color: #ffffff;
2668 2743 }
2669 2744 .right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
2670 2745 text-decoration: none;
2671 2746 color: #ffffff;
2672 2747 }
2673 2748 div.browserblock {
2674 2749 overflow: hidden;
2675 2750 border: 1px solid #ccc;
2676 2751 background: #f8f8f8;
2677 2752 font-size: 100%;
2678 2753 line-height: 125%;
2679 2754 padding: 0;
2680 2755 -webkit-border-radius: 6px 6px 0px 0px;
2681 2756 -moz-border-radius: 6px 6px 0px 0px;
2682 2757 border-radius: 6px 6px 0px 0px;
2683 2758 }
2684 2759
2685 2760 div.browserblock .browser-header {
2686 2761 background: #FFF;
2687 2762 padding: 10px 0px 15px 0px;
2688 2763 width: 100%;
2689 2764 }
2690 2765
2691 2766 div.browserblock .browser-nav {
2692 2767 float: left
2693 2768 }
2694 2769
2695 2770 div.browserblock .browser-branch {
2696 2771 float: left;
2697 2772 }
2698 2773
2699 2774 div.browserblock .browser-branch label {
2700 2775 color: #4A4A4A;
2701 2776 vertical-align: text-top;
2702 2777 }
2703 2778
2704 2779 div.browserblock .browser-header span {
2705 2780 margin-left: 5px;
2706 2781 font-weight: 700;
2707 2782 }
2708 2783
2709 2784 div.browserblock .browser-search {
2710 2785 clear: both;
2711 2786 padding: 8px 8px 0px 5px;
2712 2787 height: 20px;
2713 2788 }
2714 2789
2715 2790 div.browserblock #node_filter_box {
2716 2791
2717 2792 }
2718 2793
2719 2794 div.browserblock .search_activate {
2720 2795 float: left
2721 2796 }
2722 2797
2723 2798 div.browserblock .add_node {
2724 2799 float: left;
2725 2800 padding-left: 5px;
2726 2801 }
2727 2802
2728 2803 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2729 2804 {
2730 2805 text-decoration: none !important;
2731 2806 }
2732 2807
2733 2808 div.browserblock .browser-body {
2734 2809 background: #EEE;
2735 2810 border-top: 1px solid #CCC;
2736 2811 }
2737 2812
2738 2813 table.code-browser {
2739 2814 border-collapse: collapse;
2740 2815 width: 100%;
2741 2816 }
2742 2817
2743 2818 table.code-browser tr {
2744 2819 margin: 3px;
2745 2820 }
2746 2821
2747 2822 table.code-browser thead th {
2748 2823 background-color: #EEE;
2749 2824 height: 20px;
2750 2825 font-size: 1.1em;
2751 2826 font-weight: 700;
2752 2827 text-align: left;
2753 2828 padding-left: 10px;
2754 2829 }
2755 2830
2756 2831 table.code-browser tbody td {
2757 2832 padding-left: 10px;
2758 2833 height: 20px;
2759 2834 }
2760 2835
2761 2836 table.code-browser .browser-file {
2762 2837 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2763 2838 height: 16px;
2764 2839 padding-left: 20px;
2765 2840 text-align: left;
2766 2841 }
2767 2842 .diffblock .changeset_header {
2768 2843 height: 16px;
2769 2844 }
2770 2845 .diffblock .changeset_file {
2771 2846 background: url("../images/icons/file.png") no-repeat scroll 3px;
2772 2847 text-align: left;
2773 2848 float: left;
2774 2849 padding: 2px 0px 2px 22px;
2775 2850 }
2776 2851 .diffblock .diff-menu-wrapper{
2777 2852 float: left;
2778 2853 }
2779 2854
2780 2855 .diffblock .diff-menu{
2781 2856 position: absolute;
2782 2857 background: none repeat scroll 0 0 #FFFFFF;
2783 2858 border-color: #003367 #666666 #666666;
2784 2859 border-right: 1px solid #666666;
2785 2860 border-style: solid solid solid;
2786 2861 border-width: 1px;
2787 2862 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2788 2863 margin-top:5px;
2789 2864 margin-left:1px;
2790 2865
2791 2866 }
2792 2867 .diffblock .diff-actions {
2793 2868 padding: 2px 0px 0px 2px;
2794 2869 float: left;
2795 2870 }
2796 2871 .diffblock .diff-menu ul li {
2797 2872 padding: 0px 0px 0px 0px !important;
2798 2873 }
2799 2874 .diffblock .diff-menu ul li a{
2800 2875 display: block;
2801 2876 padding: 3px 8px 3px 8px !important;
2802 2877 }
2803 2878 .diffblock .diff-menu ul li a:hover{
2804 2879 text-decoration: none;
2805 2880 background-color: #EEEEEE;
2806 2881 }
2807 2882 table.code-browser .browser-dir {
2808 2883 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2809 2884 height: 16px;
2810 2885 padding-left: 20px;
2811 2886 text-align: left;
2812 2887 }
2813 2888
2814 2889 table.code-browser .submodule-dir {
2815 2890 background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
2816 2891 height: 16px;
2817 2892 padding-left: 20px;
2818 2893 text-align: left;
2819 2894 }
2820 2895
2821 2896
2822 2897 .box .search {
2823 2898 clear: both;
2824 2899 overflow: hidden;
2825 2900 margin: 0;
2826 2901 padding: 0 20px 10px;
2827 2902 }
2828 2903
2829 2904 .box .search div.search_path {
2830 2905 background: none repeat scroll 0 0 #EEE;
2831 2906 border: 1px solid #CCC;
2832 2907 color: blue;
2833 2908 margin-bottom: 10px;
2834 2909 padding: 10px 0;
2835 2910 }
2836 2911
2837 2912 .box .search div.search_path div.link {
2838 2913 font-weight: 700;
2839 2914 margin-left: 25px;
2840 2915 }
2841 2916
2842 2917 .box .search div.search_path div.link a {
2843 2918 color: #003367;
2844 2919 cursor: pointer;
2845 2920 text-decoration: none;
2846 2921 }
2847 2922
2848 2923 #path_unlock {
2849 2924 color: red;
2850 2925 font-size: 1.2em;
2851 2926 padding-left: 4px;
2852 2927 }
2853 2928
2854 2929 .info_box span {
2855 2930 margin-left: 3px;
2856 2931 margin-right: 3px;
2857 2932 }
2858 2933
2859 2934 .info_box .rev {
2860 2935 color: #003367;
2861 2936 font-size: 1.6em;
2862 2937 font-weight: bold;
2863 2938 vertical-align: sub;
2864 2939 }
2865 2940
2866 2941 .info_box input#at_rev,.info_box input#size {
2867 2942 background: #FFF;
2868 2943 border-top: 1px solid #b3b3b3;
2869 2944 border-left: 1px solid #b3b3b3;
2870 2945 border-right: 1px solid #eaeaea;
2871 2946 border-bottom: 1px solid #eaeaea;
2872 2947 color: #000;
2873 2948 font-size: 12px;
2874 2949 margin: 0;
2875 2950 padding: 1px 5px 1px;
2876 2951 }
2877 2952
2878 2953 .info_box input#view {
2879 2954 text-align: center;
2880 2955 padding: 4px 3px 2px 2px;
2881 2956 }
2882 2957
2883 2958 .yui-overlay,.yui-panel-container {
2884 2959 visibility: hidden;
2885 2960 position: absolute;
2886 2961 z-index: 2;
2887 2962 }
2888 2963
2889 2964 .yui-tt {
2890 2965 visibility: hidden;
2891 2966 position: absolute;
2892 2967 color: #666;
2893 2968 background-color: #FFF;
2894 2969 border: 2px solid #003367;
2895 2970 font: 100% sans-serif;
2896 2971 width: auto;
2897 2972 opacity: 1px;
2898 2973 padding: 8px;
2899 2974 white-space: pre-wrap;
2900 2975 -webkit-border-radius: 8px 8px 8px 8px;
2901 2976 -khtml-border-radius: 8px 8px 8px 8px;
2902 2977 -moz-border-radius: 8px 8px 8px 8px;
2903 2978 border-radius: 8px 8px 8px 8px;
2904 2979 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2905 2980 }
2906 2981
2907 2982 .mentions-container{
2908 2983 width: 90% !important;
2909 2984 }
2910 2985 .mentions-container .yui-ac-content{
2911 2986 width: 100% !important;
2912 2987 }
2913 2988
2914 2989 .ac {
2915 2990 vertical-align: top;
2916 2991 }
2917 2992
2918 2993 .ac .yui-ac {
2919 2994 position: inherit;
2920 2995 font-size: 100%;
2921 2996 }
2922 2997
2923 2998 .ac .perm_ac {
2924 2999 width: 20em;
2925 3000 }
2926 3001
2927 3002 .ac .yui-ac-input {
2928 3003 width: 100%;
2929 3004 }
2930 3005
2931 3006 .ac .yui-ac-container {
2932 3007 position: absolute;
2933 3008 top: 1.6em;
2934 3009 width: auto;
2935 3010 }
2936 3011
2937 3012 .ac .yui-ac-content {
2938 3013 position: absolute;
2939 3014 border: 1px solid gray;
2940 3015 background: #fff;
2941 3016 z-index: 9050;
2942 3017
2943 3018 }
2944 3019
2945 3020 .ac .yui-ac-shadow {
2946 3021 position: absolute;
2947 3022 width: 100%;
2948 3023 background: #000;
2949 3024 -moz-opacity: 0.1px;
2950 3025 opacity: .10;
2951 3026 filter: alpha(opacity = 10);
2952 3027 z-index: 9049;
2953 3028 margin: .3em;
2954 3029 }
2955 3030
2956 3031 .ac .yui-ac-content ul {
2957 3032 width: 100%;
2958 3033 margin: 0;
2959 3034 padding: 0;
2960 3035 z-index: 9050;
2961 3036 }
2962 3037
2963 3038 .ac .yui-ac-content li {
2964 3039 cursor: default;
2965 3040 white-space: nowrap;
2966 3041 margin: 0;
2967 3042 padding: 2px 5px;
2968 3043 height: 18px;
2969 3044 z-index: 9050;
2970 3045 display: block;
2971 3046 width: auto !important;
2972 3047 }
2973 3048
2974 3049 .ac .yui-ac-content li .ac-container-wrap{
2975 3050 width: auto;
2976 3051 }
2977 3052
2978 3053 .ac .yui-ac-content li.yui-ac-prehighlight {
2979 3054 background: #B3D4FF;
2980 3055 z-index: 9050;
2981 3056 }
2982 3057
2983 3058 .ac .yui-ac-content li.yui-ac-highlight {
2984 3059 background: #556CB5;
2985 3060 color: #FFF;
2986 3061 z-index: 9050;
2987 3062 }
2988 3063 .ac .yui-ac-bd{
2989 3064 z-index: 9050;
2990 3065 }
2991 3066
2992 3067 .follow {
2993 3068 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
2994 3069 height: 16px;
2995 3070 width: 20px;
2996 3071 cursor: pointer;
2997 3072 display: block;
2998 3073 float: right;
2999 3074 margin-top: 2px;
3000 3075 }
3001 3076
3002 3077 .following {
3003 3078 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3004 3079 height: 16px;
3005 3080 width: 20px;
3006 3081 cursor: pointer;
3007 3082 display: block;
3008 3083 float: right;
3009 3084 margin-top: 2px;
3010 3085 }
3011 3086
3012 3087 .currently_following {
3013 3088 padding-left: 10px;
3014 3089 padding-bottom: 5px;
3015 3090 }
3016 3091
3017 3092 .add_icon {
3018 3093 background: url("../images/icons/add.png") no-repeat scroll 3px;
3019 3094 padding-left: 20px;
3020 3095 padding-top: 0px;
3021 3096 text-align: left;
3022 3097 }
3023 3098
3024 3099 .accept_icon {
3025 3100 background: url("../images/icons/accept.png") no-repeat scroll 3px;
3026 3101 padding-left: 20px;
3027 3102 padding-top: 0px;
3028 3103 text-align: left;
3029 3104 }
3030 3105
3031 3106 .edit_icon {
3032 3107 background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
3033 3108 padding-left: 20px;
3034 3109 padding-top: 0px;
3035 3110 text-align: left;
3036 3111 }
3037 3112
3038 3113 .delete_icon {
3039 3114 background: url("../images/icons/delete.png") no-repeat scroll 3px;
3040 3115 padding-left: 20px;
3041 3116 padding-top: 0px;
3042 3117 text-align: left;
3043 3118 }
3044 3119
3045 3120 .refresh_icon {
3046 3121 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3047 3122 3px;
3048 3123 padding-left: 20px;
3049 3124 padding-top: 0px;
3050 3125 text-align: left;
3051 3126 }
3052 3127
3053 3128 .pull_icon {
3054 3129 background: url("../images/icons/connect.png") no-repeat scroll 3px;
3055 3130 padding-left: 20px;
3056 3131 padding-top: 0px;
3057 3132 text-align: left;
3058 3133 }
3059 3134
3060 3135 .rss_icon {
3061 3136 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
3062 3137 padding-left: 20px;
3063 3138 padding-top: 4px;
3064 3139 text-align: left;
3065 3140 font-size: 8px
3066 3141 }
3067 3142
3068 3143 .atom_icon {
3069 3144 background: url("../images/icons/atom.png") no-repeat scroll 3px;
3070 3145 padding-left: 20px;
3071 3146 padding-top: 4px;
3072 3147 text-align: left;
3073 3148 font-size: 8px
3074 3149 }
3075 3150
3076 3151 .archive_icon {
3077 3152 background: url("../images/icons/compress.png") no-repeat scroll 3px;
3078 3153 padding-left: 20px;
3079 3154 text-align: left;
3080 3155 padding-top: 1px;
3081 3156 }
3082 3157
3083 3158 .start_following_icon {
3084 3159 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3085 3160 padding-left: 20px;
3086 3161 text-align: left;
3087 3162 padding-top: 0px;
3088 3163 }
3089 3164
3090 3165 .stop_following_icon {
3091 3166 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3092 3167 padding-left: 20px;
3093 3168 text-align: left;
3094 3169 padding-top: 0px;
3095 3170 }
3096 3171
3097 3172 .action_button {
3098 3173 border: 0;
3099 3174 display: inline;
3100 3175 }
3101 3176
3102 3177 .action_button:hover {
3103 3178 border: 0;
3104 3179 text-decoration: underline;
3105 3180 cursor: pointer;
3106 3181 }
3107 3182
3108 3183 #switch_repos {
3109 3184 position: absolute;
3110 3185 height: 25px;
3111 3186 z-index: 1;
3112 3187 }
3113 3188
3114 3189 #switch_repos select {
3115 3190 min-width: 150px;
3116 3191 max-height: 250px;
3117 3192 z-index: 1;
3118 3193 }
3119 3194
3120 3195 .breadcrumbs {
3121 3196 border: medium none;
3122 3197 color: #FFF;
3123 3198 float: left;
3124 3199 text-transform: uppercase;
3125 3200 font-weight: 700;
3126 3201 font-size: 14px;
3127 3202 margin: 0;
3128 3203 padding: 11px 0 11px 10px;
3129 3204 }
3130 3205
3131 3206 .breadcrumbs .hash {
3132 3207 text-transform: none;
3133 3208 color: #fff;
3134 3209 }
3135 3210
3136 3211 .breadcrumbs a {
3137 3212 color: #FFF;
3138 3213 }
3139 3214
3140 3215 .flash_msg {
3141 3216
3142 3217 }
3143 3218
3144 3219 .flash_msg ul {
3145 3220
3146 3221 }
3147 3222
3148 3223 .error_msg {
3149 3224 background-color: #c43c35;
3150 3225 background-repeat: repeat-x;
3151 3226 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3152 3227 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3153 3228 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3154 3229 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3155 3230 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3156 3231 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3157 3232 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3158 3233 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3159 3234 border-color: #c43c35 #c43c35 #882a25;
3160 3235 }
3161 3236
3162 3237 .warning_msg {
3163 3238 color: #404040 !important;
3164 3239 background-color: #eedc94;
3165 3240 background-repeat: repeat-x;
3166 3241 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3167 3242 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3168 3243 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3169 3244 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3170 3245 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3171 3246 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3172 3247 background-image: linear-gradient(top, #fceec1, #eedc94);
3173 3248 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3174 3249 border-color: #eedc94 #eedc94 #e4c652;
3175 3250 }
3176 3251
3177 3252 .success_msg {
3178 3253 background-color: #57a957;
3179 3254 background-repeat: repeat-x !important;
3180 3255 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3181 3256 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3182 3257 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3183 3258 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3184 3259 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3185 3260 background-image: -o-linear-gradient(top, #62c462, #57a957);
3186 3261 background-image: linear-gradient(top, #62c462, #57a957);
3187 3262 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3188 3263 border-color: #57a957 #57a957 #3d773d;
3189 3264 }
3190 3265
3191 3266 .notice_msg {
3192 3267 background-color: #339bb9;
3193 3268 background-repeat: repeat-x;
3194 3269 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3195 3270 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3196 3271 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3197 3272 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3198 3273 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3199 3274 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3200 3275 background-image: linear-gradient(top, #5bc0de, #339bb9);
3201 3276 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3202 3277 border-color: #339bb9 #339bb9 #22697d;
3203 3278 }
3204 3279
3205 3280 .success_msg,.error_msg,.notice_msg,.warning_msg {
3206 3281 font-size: 12px;
3207 3282 font-weight: 700;
3208 3283 min-height: 14px;
3209 3284 line-height: 14px;
3210 3285 margin-bottom: 10px;
3211 3286 margin-top: 0;
3212 3287 display: block;
3213 3288 overflow: auto;
3214 3289 padding: 6px 10px 6px 10px;
3215 3290 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3216 3291 position: relative;
3217 3292 color: #FFF;
3218 3293 border-width: 1px;
3219 3294 border-style: solid;
3220 3295 -webkit-border-radius: 4px;
3221 3296 -moz-border-radius: 4px;
3222 3297 border-radius: 4px;
3223 3298 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3224 3299 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3225 3300 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3226 3301 }
3227 3302
3228 3303 #msg_close {
3229 3304 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3230 3305 cursor: pointer;
3231 3306 height: 16px;
3232 3307 position: absolute;
3233 3308 right: 5px;
3234 3309 top: 5px;
3235 3310 width: 16px;
3236 3311 }
3237 3312 div#legend_data{
3238 3313 padding-left:10px;
3239 3314 }
3240 3315 div#legend_container table{
3241 3316 border: none !important;
3242 3317 }
3243 3318 div#legend_container table,div#legend_choices table {
3244 3319 width: auto !important;
3245 3320 }
3246 3321
3247 3322 table#permissions_manage {
3248 3323 width: 0 !important;
3249 3324 }
3250 3325
3251 3326 table#permissions_manage span.private_repo_msg {
3252 3327 font-size: 0.8em;
3253 3328 opacity: 0.6px;
3254 3329 }
3255 3330
3256 3331 table#permissions_manage td.private_repo_msg {
3257 3332 font-size: 0.8em;
3258 3333 }
3259 3334
3260 3335 table#permissions_manage tr#add_perm_input td {
3261 3336 vertical-align: middle;
3262 3337 }
3263 3338
3264 3339 div.gravatar {
3265 3340 background-color: #FFF;
3266 3341 float: left;
3267 3342 margin-right: 0.7em;
3268 3343 padding: 1px 1px 1px 1px;
3269 3344 line-height:0;
3270 3345 -webkit-border-radius: 3px;
3271 3346 -khtml-border-radius: 3px;
3272 3347 -moz-border-radius: 3px;
3273 3348 border-radius: 3px;
3274 3349 }
3275 3350
3276 3351 div.gravatar img {
3277 3352 -webkit-border-radius: 2px;
3278 3353 -khtml-border-radius: 2px;
3279 3354 -moz-border-radius: 2px;
3280 3355 border-radius: 2px;
3281 3356 }
3282 3357
3283 3358 #header,#content,#footer {
3284 3359 min-width: 978px;
3285 3360 }
3286 3361
3287 3362 #content {
3288 3363 clear: both;
3289 3364 overflow: hidden;
3290 3365 padding: 54px 10px 14px 10px;
3291 3366 }
3292 3367
3293 3368 #content div.box div.title div.search {
3294 3369
3295 3370 border-left: 1px solid #316293;
3296 3371 }
3297 3372
3298 3373 #content div.box div.title div.search div.input input {
3299 3374 border: 1px solid #316293;
3300 3375 }
3301 3376
3302 3377 .ui-btn{
3303 3378 color: #515151;
3304 3379 background-color: #DADADA;
3305 3380 background-repeat: repeat-x;
3306 3381 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3307 3382 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3308 3383 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3309 3384 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3310 3385 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3311 3386 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3312 3387 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3313 3388 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3314 3389
3315 3390 border-top: 1px solid #DDD;
3316 3391 border-left: 1px solid #c6c6c6;
3317 3392 border-right: 1px solid #DDD;
3318 3393 border-bottom: 1px solid #c6c6c6;
3319 3394 color: #515151;
3320 3395 outline: none;
3321 3396 margin: 0px 3px 3px 0px;
3322 3397 -webkit-border-radius: 4px 4px 4px 4px !important;
3323 3398 -khtml-border-radius: 4px 4px 4px 4px !important;
3324 3399 -moz-border-radius: 4px 4px 4px 4px !important;
3325 3400 border-radius: 4px 4px 4px 4px !important;
3326 3401 cursor: pointer !important;
3327 3402 padding: 3px 3px 3px 3px;
3328 3403 background-position: 0 -15px;
3329 3404
3330 3405 }
3331 3406 .ui-btn.xsmall{
3332 3407 padding: 1px 2px 1px 1px;
3333 3408 }
3334 3409
3335 3410 .ui-btn.large{
3336 3411 padding: 6px 12px;
3337 3412 }
3338 3413
3339 3414 .ui-btn.clone{
3340 3415 padding: 5px 2px 6px 1px;
3341 3416 margin: 0px -4px 3px 0px;
3342 3417 -webkit-border-radius: 4px 0px 0px 4px !important;
3343 3418 -khtml-border-radius: 4px 0px 0px 4px !important;
3344 3419 -moz-border-radius: 4px 0px 0px 4px !important;
3345 3420 border-radius: 4px 0px 0px 4px !important;
3346 3421 width: 100px;
3347 3422 text-align: center;
3348 3423 float: left;
3349 3424 position: absolute;
3350 3425 }
3351 3426 .ui-btn:focus {
3352 3427 outline: none;
3353 3428 }
3354 3429 .ui-btn:hover{
3355 3430 background-position: 0 0px;
3356 3431 text-decoration: none;
3357 3432 color: #515151;
3358 3433 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3359 3434 }
3360 3435
3361 3436 .ui-btn.red{
3362 3437 color:#fff;
3363 3438 background-color: #c43c35;
3364 3439 background-repeat: repeat-x;
3365 3440 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3366 3441 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3367 3442 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3368 3443 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3369 3444 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3370 3445 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3371 3446 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3372 3447 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3373 3448 border-color: #c43c35 #c43c35 #882a25;
3374 3449 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3375 3450 }
3376 3451
3377 3452
3378 3453 .ui-btn.blue{
3379 3454 color:#fff;
3380 3455 background-color: #339bb9;
3381 3456 background-repeat: repeat-x;
3382 3457 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3383 3458 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3384 3459 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3385 3460 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3386 3461 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3387 3462 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3388 3463 background-image: linear-gradient(top, #5bc0de, #339bb9);
3389 3464 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3390 3465 border-color: #339bb9 #339bb9 #22697d;
3391 3466 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3392 3467 }
3393 3468
3394 3469 .ui-btn.green{
3395 3470 background-color: #57a957;
3396 3471 background-repeat: repeat-x;
3397 3472 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3398 3473 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3399 3474 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3400 3475 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3401 3476 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3402 3477 background-image: -o-linear-gradient(top, #62c462, #57a957);
3403 3478 background-image: linear-gradient(top, #62c462, #57a957);
3404 3479 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3405 3480 border-color: #57a957 #57a957 #3d773d;
3406 3481 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3407 3482 }
3408 3483
3409 3484 .ui-btn.active{
3410 3485 font-weight: bold;
3411 3486 }
3412 3487
3413 3488 ins,div.options a:hover {
3414 3489 text-decoration: none;
3415 3490 }
3416 3491
3417 3492 img,
3418 3493 #header #header-inner #quick li a:hover span.normal,
3419 3494 #header #header-inner #quick li ul li.last,
3420 3495 #content div.box div.form div.fields div.field div.textarea table td table td a,
3421 3496 #clone_url,
3422 3497 #clone_url_id
3423 3498 {
3424 3499 border: none;
3425 3500 }
3426 3501
3427 3502 img.icon,.right .merge img {
3428 3503 vertical-align: bottom;
3429 3504 }
3430 3505
3431 3506 #header ul#logged-user,#content div.box div.title ul.links,
3432 3507 #content div.box div.message div.dismiss,
3433 3508 #content div.box div.traffic div.legend ul
3434 3509 {
3435 3510 float: right;
3436 3511 margin: 0;
3437 3512 padding: 0;
3438 3513 }
3439 3514
3440 3515 #header #header-inner #home,#header #header-inner #logo,
3441 3516 #content div.box ul.left,#content div.box ol.left,
3442 3517 #content div.box div.pagination-left,div#commit_history,
3443 3518 div#legend_data,div#legend_container,div#legend_choices
3444 3519 {
3445 3520 float: left;
3446 3521 }
3447 3522
3448 3523 #header #header-inner #quick li:hover ul ul,
3449 3524 #header #header-inner #quick li:hover ul ul ul,
3450 3525 #header #header-inner #quick li:hover ul ul ul ul,
3451 3526 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3452 3527 {
3453 3528 display: none;
3454 3529 }
3455 3530
3456 3531 #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
3457 3532 {
3458 3533 display: block;
3459 3534 }
3460 3535
3461 3536 #content div.graph {
3462 3537 padding: 0 10px 10px;
3463 3538 }
3464 3539
3465 3540 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3466 3541 {
3467 3542 color: #bfe3ff;
3468 3543 }
3469 3544
3470 3545 #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
3471 3546 {
3472 3547 margin: 10px 24px 10px 44px;
3473 3548 }
3474 3549
3475 3550 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3476 3551 {
3477 3552 clear: both;
3478 3553 overflow: hidden;
3479 3554 margin: 0;
3480 3555 padding: 0 20px 10px;
3481 3556 }
3482 3557
3483 3558 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3484 3559 {
3485 3560 clear: both;
3486 3561 overflow: hidden;
3487 3562 margin: 0;
3488 3563 padding: 0;
3489 3564 }
3490 3565
3491 3566 #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
3492 3567 {
3493 3568 height: 1%;
3494 3569 display: block;
3495 3570 color: #363636;
3496 3571 margin: 0;
3497 3572 padding: 2px 0 0;
3498 3573 }
3499 3574
3500 3575 #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
3501 3576 {
3502 3577 background: #FBE3E4;
3503 3578 border-top: 1px solid #e1b2b3;
3504 3579 border-left: 1px solid #e1b2b3;
3505 3580 border-right: 1px solid #FBC2C4;
3506 3581 border-bottom: 1px solid #FBC2C4;
3507 3582 }
3508 3583
3509 3584 #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
3510 3585 {
3511 3586 background: #E6EFC2;
3512 3587 border-top: 1px solid #cebb98;
3513 3588 border-left: 1px solid #cebb98;
3514 3589 border-right: 1px solid #c6d880;
3515 3590 border-bottom: 1px solid #c6d880;
3516 3591 }
3517 3592
3518 3593 #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
3519 3594 {
3520 3595 margin: 0;
3521 3596 }
3522 3597
3523 3598 #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
3524 3599 {
3525 3600 margin: 0 0 0 0px !important;
3526 3601 padding: 0;
3527 3602 }
3528 3603
3529 3604 #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
3530 3605 {
3531 3606 margin: 0 0 0 200px;
3532 3607 padding: 0;
3533 3608 }
3534 3609
3535 3610 #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
3536 3611 {
3537 3612 color: #000;
3538 3613 text-decoration: none;
3539 3614 }
3540 3615
3541 3616 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3542 3617 {
3543 3618 border: 1px solid #666;
3544 3619 }
3545 3620
3546 3621 #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
3547 3622 {
3548 3623 clear: both;
3549 3624 overflow: hidden;
3550 3625 margin: 0;
3551 3626 padding: 8px 0 2px;
3552 3627 }
3553 3628
3554 3629 #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
3555 3630 {
3556 3631 float: left;
3557 3632 margin: 0;
3558 3633 }
3559 3634
3560 3635 #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
3561 3636 {
3562 3637 height: 1%;
3563 3638 display: block;
3564 3639 float: left;
3565 3640 margin: 2px 0 0 4px;
3566 3641 }
3567 3642
3568 3643 div.form div.fields div.field div.button input,
3569 3644 #content div.box div.form div.fields div.buttons input
3570 3645 div.form div.fields div.buttons input,
3571 3646 #content div.box div.action div.button input {
3572 3647 /*color: #000;*/
3573 3648 font-size: 11px;
3574 3649 font-weight: 700;
3575 3650 margin: 0;
3576 3651 }
3577 3652
3578 3653 input.ui-button {
3579 3654 background: #e5e3e3 url("../images/button.png") repeat-x;
3580 3655 border-top: 1px solid #DDD;
3581 3656 border-left: 1px solid #c6c6c6;
3582 3657 border-right: 1px solid #DDD;
3583 3658 border-bottom: 1px solid #c6c6c6;
3584 3659 color: #515151 !important;
3585 3660 outline: none;
3586 3661 margin: 0;
3587 3662 padding: 6px 12px;
3588 3663 -webkit-border-radius: 4px 4px 4px 4px;
3589 3664 -khtml-border-radius: 4px 4px 4px 4px;
3590 3665 -moz-border-radius: 4px 4px 4px 4px;
3591 3666 border-radius: 4px 4px 4px 4px;
3592 3667 box-shadow: 0 1px 0 #ececec;
3593 3668 cursor: pointer;
3594 3669 }
3595 3670
3596 3671 input.ui-button:hover {
3597 3672 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3598 3673 border-top: 1px solid #ccc;
3599 3674 border-left: 1px solid #bebebe;
3600 3675 border-right: 1px solid #b1b1b1;
3601 3676 border-bottom: 1px solid #afafaf;
3602 3677 }
3603 3678
3604 3679 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3605 3680 {
3606 3681 display: inline;
3607 3682 }
3608 3683
3609 3684 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3610 3685 {
3611 3686 margin: 10px 0 0 200px;
3612 3687 padding: 0;
3613 3688 }
3614 3689
3615 3690 #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
3616 3691 {
3617 3692 margin: 10px 0 0;
3618 3693 }
3619 3694
3620 3695 #content div.box table td.user,#content div.box table td.address {
3621 3696 width: 10%;
3622 3697 text-align: center;
3623 3698 }
3624 3699
3625 3700 #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
3626 3701 {
3627 3702 text-align: right;
3628 3703 margin: 6px 0 0;
3629 3704 padding: 0;
3630 3705 }
3631 3706
3632 3707 #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
3633 3708 {
3634 3709 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3635 3710 border-top: 1px solid #ccc;
3636 3711 border-left: 1px solid #bebebe;
3637 3712 border-right: 1px solid #b1b1b1;
3638 3713 border-bottom: 1px solid #afafaf;
3639 3714 color: #515151;
3640 3715 margin: 0;
3641 3716 padding: 6px 12px;
3642 3717 }
3643 3718
3644 3719 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3645 3720 {
3646 3721 text-align: left;
3647 3722 float: left;
3648 3723 margin: 0;
3649 3724 padding: 0;
3650 3725 }
3651 3726
3652 3727 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3653 3728 {
3654 3729 height: 1%;
3655 3730 display: block;
3656 3731 float: left;
3657 3732 background: #ebebeb url("../images/pager.png") repeat-x;
3658 3733 border-top: 1px solid #dedede;
3659 3734 border-left: 1px solid #cfcfcf;
3660 3735 border-right: 1px solid #c4c4c4;
3661 3736 border-bottom: 1px solid #c4c4c4;
3662 3737 color: #4A4A4A;
3663 3738 font-weight: 700;
3664 3739 margin: 0;
3665 3740 padding: 6px 8px;
3666 3741 }
3667 3742
3668 3743 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3669 3744 {
3670 3745 color: #B4B4B4;
3671 3746 padding: 6px;
3672 3747 }
3673 3748
3674 3749 #login,#register {
3675 3750 width: 520px;
3676 3751 margin: 10% auto 0;
3677 3752 padding: 0;
3678 3753 }
3679 3754
3680 3755 #login div.color,#register div.color {
3681 3756 clear: both;
3682 3757 overflow: hidden;
3683 3758 background: #FFF;
3684 3759 margin: 10px auto 0;
3685 3760 padding: 3px 3px 3px 0;
3686 3761 }
3687 3762
3688 3763 #login div.color a,#register div.color a {
3689 3764 width: 20px;
3690 3765 height: 20px;
3691 3766 display: block;
3692 3767 float: left;
3693 3768 margin: 0 0 0 3px;
3694 3769 padding: 0;
3695 3770 }
3696 3771
3697 3772 #login div.title h5,#register div.title h5 {
3698 3773 color: #fff;
3699 3774 margin: 10px;
3700 3775 padding: 0;
3701 3776 }
3702 3777
3703 3778 #login div.form div.fields div.field,#register div.form div.fields div.field
3704 3779 {
3705 3780 clear: both;
3706 3781 overflow: hidden;
3707 3782 margin: 0;
3708 3783 padding: 0 0 10px;
3709 3784 }
3710 3785
3711 3786 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3712 3787 {
3713 3788 height: 1%;
3714 3789 display: block;
3715 3790 color: red;
3716 3791 margin: 8px 0 0;
3717 3792 padding: 0;
3718 3793 max-width: 320px;
3719 3794 }
3720 3795
3721 3796 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3722 3797 {
3723 3798 color: #000;
3724 3799 font-weight: 700;
3725 3800 }
3726 3801
3727 3802 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3728 3803 {
3729 3804 float: left;
3730 3805 margin: 0;
3731 3806 padding: 0;
3732 3807 }
3733 3808
3734 3809 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3735 3810 {
3736 3811 margin: 0 0 0 184px;
3737 3812 padding: 0;
3738 3813 }
3739 3814
3740 3815 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3741 3816 {
3742 3817 color: #565656;
3743 3818 font-weight: 700;
3744 3819 }
3745 3820
3746 3821 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3747 3822 {
3748 3823 color: #000;
3749 3824 font-size: 1em;
3750 3825 font-weight: 700;
3751 3826 margin: 0;
3752 3827 }
3753 3828
3754 3829 #changeset_content .container .wrapper,#graph_content .container .wrapper
3755 3830 {
3756 3831 width: 600px;
3757 3832 }
3758 3833
3759 3834 #changeset_content .container .left {
3760 3835 float: left;
3761 3836 width: 75%;
3762 3837 padding-left: 5px;
3763 3838 }
3764 3839
3765 3840 #changeset_content .container .left .date,.ac .match {
3766 3841 font-weight: 700;
3767 3842 padding-top: 5px;
3768 3843 padding-bottom: 5px;
3769 3844 }
3770 3845
3771 3846 div#legend_container table td,div#legend_choices table td {
3772 3847 border: none !important;
3773 3848 height: 20px !important;
3774 3849 padding: 0 !important;
3775 3850 }
3776 3851
3777 3852 .q_filter_box {
3778 3853 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3779 3854 -webkit-border-radius: 4px;
3780 3855 -moz-border-radius: 4px;
3781 3856 border-radius: 4px;
3782 3857 border: 0 none;
3783 3858 color: #AAAAAA;
3784 3859 margin-bottom: -4px;
3785 3860 margin-top: -4px;
3786 3861 padding-left: 3px;
3787 3862 }
3788 3863
3789 3864 #node_filter {
3790 3865 border: 0px solid #545454;
3791 3866 color: #AAAAAA;
3792 3867 padding-left: 3px;
3793 3868 }
3794 3869
3795 3870
3796 3871 .group_members_wrap{
3797 3872
3798 3873 }
3799 3874
3800 3875 .group_members .group_member{
3801 3876 height: 30px;
3802 3877 padding:0px 0px 0px 10px;
3803 3878 }
3804 3879
3805 3880 .reviewers_member{
3806 3881 height: 15px;
3807 3882 padding:0px 0px 0px 10px;
3808 3883 }
3809 3884
3810 3885 .emails_wrap{
3811 3886 padding: 0px 20px;
3812 3887 }
3813 3888
3814 3889 .emails_wrap .email_entry{
3815 3890 height: 30px;
3816 3891 padding:0px 0px 0px 10px;
3817 3892 }
3818 3893 .emails_wrap .email_entry .email{
3819 3894 float: left
3820 3895 }
3821 3896 .emails_wrap .email_entry .email_action{
3822 3897 float: left
3823 3898 }
3824 3899
3825 3900 /*README STYLE*/
3826 3901
3827 3902 div.readme {
3828 3903 padding:0px;
3829 3904 }
3830 3905
3831 3906 div.readme h2 {
3832 3907 font-weight: normal;
3833 3908 }
3834 3909
3835 3910 div.readme .readme_box {
3836 3911 background-color: #fafafa;
3837 3912 }
3838 3913
3839 3914 div.readme .readme_box {
3840 3915 clear:both;
3841 3916 overflow:hidden;
3842 3917 margin:0;
3843 3918 padding:0 20px 10px;
3844 3919 }
3845 3920
3846 3921 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
3847 3922 border-bottom: 0 !important;
3848 3923 margin: 0 !important;
3849 3924 padding: 0 !important;
3850 3925 line-height: 1.5em !important;
3851 3926 }
3852 3927
3853 3928
3854 3929 div.readme .readme_box h1:first-child {
3855 3930 padding-top: .25em !important;
3856 3931 }
3857 3932
3858 3933 div.readme .readme_box h2, div.readme .readme_box h3 {
3859 3934 margin: 1em 0 !important;
3860 3935 }
3861 3936
3862 3937 div.readme .readme_box h2 {
3863 3938 margin-top: 1.5em !important;
3864 3939 border-top: 4px solid #e0e0e0 !important;
3865 3940 padding-top: .5em !important;
3866 3941 }
3867 3942
3868 3943 div.readme .readme_box p {
3869 3944 color: black !important;
3870 3945 margin: 1em 0 !important;
3871 3946 line-height: 1.5em !important;
3872 3947 }
3873 3948
3874 3949 div.readme .readme_box ul {
3875 3950 list-style: disc !important;
3876 3951 margin: 1em 0 1em 2em !important;
3877 3952 }
3878 3953
3879 3954 div.readme .readme_box ol {
3880 3955 list-style: decimal;
3881 3956 margin: 1em 0 1em 2em !important;
3882 3957 }
3883 3958
3884 3959 div.readme .readme_box pre, code {
3885 3960 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3886 3961 }
3887 3962
3888 3963 div.readme .readme_box code {
3889 3964 font-size: 12px !important;
3890 3965 background-color: ghostWhite !important;
3891 3966 color: #444 !important;
3892 3967 padding: 0 .2em !important;
3893 3968 border: 1px solid #dedede !important;
3894 3969 }
3895 3970
3896 3971 div.readme .readme_box pre code {
3897 3972 padding: 0 !important;
3898 3973 font-size: 12px !important;
3899 3974 background-color: #eee !important;
3900 3975 border: none !important;
3901 3976 }
3902 3977
3903 3978 div.readme .readme_box pre {
3904 3979 margin: 1em 0;
3905 3980 font-size: 12px;
3906 3981 background-color: #eee;
3907 3982 border: 1px solid #ddd;
3908 3983 padding: 5px;
3909 3984 color: #444;
3910 3985 overflow: auto;
3911 3986 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3912 3987 -webkit-border-radius: 3px;
3913 3988 -moz-border-radius: 3px;
3914 3989 border-radius: 3px;
3915 3990 }
3916 3991
3917 3992
3918 3993 /** RST STYLE **/
3919 3994
3920 3995
3921 3996 div.rst-block {
3922 3997 padding:0px;
3923 3998 }
3924 3999
3925 4000 div.rst-block h2 {
3926 4001 font-weight: normal;
3927 4002 }
3928 4003
3929 4004 div.rst-block {
3930 4005 background-color: #fafafa;
3931 4006 }
3932 4007
3933 4008 div.rst-block {
3934 4009 clear:both;
3935 4010 overflow:hidden;
3936 4011 margin:0;
3937 4012 padding:0 20px 10px;
3938 4013 }
3939 4014
3940 4015 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
3941 4016 border-bottom: 0 !important;
3942 4017 margin: 0 !important;
3943 4018 padding: 0 !important;
3944 4019 line-height: 1.5em !important;
3945 4020 }
3946 4021
3947 4022
3948 4023 div.rst-block h1:first-child {
3949 4024 padding-top: .25em !important;
3950 4025 }
3951 4026
3952 4027 div.rst-block h2, div.rst-block h3 {
3953 4028 margin: 1em 0 !important;
3954 4029 }
3955 4030
3956 4031 div.rst-block h2 {
3957 4032 margin-top: 1.5em !important;
3958 4033 border-top: 4px solid #e0e0e0 !important;
3959 4034 padding-top: .5em !important;
3960 4035 }
3961 4036
3962 4037 div.rst-block p {
3963 4038 color: black !important;
3964 4039 margin: 1em 0 !important;
3965 4040 line-height: 1.5em !important;
3966 4041 }
3967 4042
3968 4043 div.rst-block ul {
3969 4044 list-style: disc !important;
3970 4045 margin: 1em 0 1em 2em !important;
3971 4046 }
3972 4047
3973 4048 div.rst-block ol {
3974 4049 list-style: decimal;
3975 4050 margin: 1em 0 1em 2em !important;
3976 4051 }
3977 4052
3978 4053 div.rst-block pre, code {
3979 4054 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
3980 4055 }
3981 4056
3982 4057 div.rst-block code {
3983 4058 font-size: 12px !important;
3984 4059 background-color: ghostWhite !important;
3985 4060 color: #444 !important;
3986 4061 padding: 0 .2em !important;
3987 4062 border: 1px solid #dedede !important;
3988 4063 }
3989 4064
3990 4065 div.rst-block pre code {
3991 4066 padding: 0 !important;
3992 4067 font-size: 12px !important;
3993 4068 background-color: #eee !important;
3994 4069 border: none !important;
3995 4070 }
3996 4071
3997 4072 div.rst-block pre {
3998 4073 margin: 1em 0;
3999 4074 font-size: 12px;
4000 4075 background-color: #eee;
4001 4076 border: 1px solid #ddd;
4002 4077 padding: 5px;
4003 4078 color: #444;
4004 4079 overflow: auto;
4005 4080 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4006 4081 -webkit-border-radius: 3px;
4007 4082 -moz-border-radius: 3px;
4008 4083 border-radius: 3px;
4009 4084 }
4010 4085
4011 4086
4012 4087 /** comment main **/
4013 4088 .comments {
4014 4089 padding:10px 20px;
4015 4090 }
4016 4091
4017 4092 .comments .comment {
4018 4093 border: 1px solid #ddd;
4019 4094 margin-top: 10px;
4020 4095 -webkit-border-radius: 4px;
4021 4096 -moz-border-radius: 4px;
4022 4097 border-radius: 4px;
4023 4098 }
4024 4099
4025 4100 .comments .comment .meta {
4026 4101 background: #f8f8f8;
4027 4102 padding: 4px;
4028 4103 border-bottom: 1px solid #ddd;
4029 4104 height: 18px;
4030 4105 }
4031 4106
4032 4107 .comments .comment .meta img {
4033 4108 vertical-align: middle;
4034 4109 }
4035 4110
4036 4111 .comments .comment .meta .user {
4037 4112 font-weight: bold;
4038 4113 float: left;
4039 4114 padding: 4px 2px 2px 2px;
4040 4115 }
4041 4116
4042 4117 .comments .comment .meta .date {
4043 4118 float: left;
4044 4119 padding:4px 4px 0px 4px;
4045 4120 }
4046 4121
4047 4122 .comments .comment .text {
4048 4123 background-color: #FAFAFA;
4049 4124 }
4050 4125 .comment .text div.rst-block p {
4051 4126 margin: 0.5em 0px !important;
4052 4127 }
4053 4128
4054 4129 .comments .comments-number{
4055 4130 padding:0px 0px 10px 0px;
4056 4131 font-weight: bold;
4057 4132 color: #666;
4058 4133 font-size: 16px;
4059 4134 }
4060 4135
4061 4136 /** comment form **/
4062 4137
4063 4138 .status-block{
4064 4139 height:80px;
4065 4140 clear:both
4066 4141 }
4067 4142
4068 4143 .comment-form .clearfix{
4069 4144 background: #EEE;
4070 4145 -webkit-border-radius: 4px;
4071 4146 -moz-border-radius: 4px;
4072 4147 border-radius: 4px;
4073 4148 padding: 10px;
4074 4149 }
4075 4150
4076 4151 div.comment-form {
4077 4152 margin-top: 20px;
4078 4153 }
4079 4154
4080 4155 .comment-form strong {
4081 4156 display: block;
4082 4157 margin-bottom: 15px;
4083 4158 }
4084 4159
4085 4160 .comment-form textarea {
4086 4161 width: 100%;
4087 4162 height: 100px;
4088 4163 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4089 4164 }
4090 4165
4091 4166 form.comment-form {
4092 4167 margin-top: 10px;
4093 4168 margin-left: 10px;
4094 4169 }
4095 4170
4096 4171 .comment-form-submit {
4097 4172 margin-top: 5px;
4098 4173 margin-left: 525px;
4099 4174 }
4100 4175
4101 4176 .file-comments {
4102 4177 display: none;
4103 4178 }
4104 4179
4105 4180 .comment-form .comment {
4106 4181 margin-left: 10px;
4107 4182 }
4108 4183
4109 4184 .comment-form .comment-help{
4110 4185 padding: 0px 0px 5px 0px;
4111 4186 color: #666;
4112 4187 }
4113 4188
4114 4189 .comment-form .comment-button{
4115 4190 padding-top:5px;
4116 4191 }
4117 4192
4118 4193 .add-another-button {
4119 4194 margin-left: 10px;
4120 4195 margin-top: 10px;
4121 4196 margin-bottom: 10px;
4122 4197 }
4123 4198
4124 4199 .comment .buttons {
4125 4200 float: right;
4126 4201 padding:2px 2px 0px 0px;
4127 4202 }
4128 4203
4129 4204
4130 4205 .show-inline-comments{
4131 4206 position: relative;
4132 4207 top:1px
4133 4208 }
4134 4209
4135 4210 /** comment inline form **/
4136 4211 .comment-inline-form .overlay{
4137 4212 display: none;
4138 4213 }
4139 4214 .comment-inline-form .overlay.submitting{
4140 4215 display:block;
4141 4216 background: none repeat scroll 0 0 white;
4142 4217 font-size: 16px;
4143 4218 opacity: 0.5;
4144 4219 position: absolute;
4145 4220 text-align: center;
4146 4221 vertical-align: top;
4147 4222
4148 4223 }
4149 4224 .comment-inline-form .overlay.submitting .overlay-text{
4150 4225 width:100%;
4151 4226 margin-top:5%;
4152 4227 }
4153 4228
4154 4229 .comment-inline-form .clearfix{
4155 4230 background: #EEE;
4156 4231 -webkit-border-radius: 4px;
4157 4232 -moz-border-radius: 4px;
4158 4233 border-radius: 4px;
4159 4234 padding: 5px;
4160 4235 }
4161 4236
4162 4237 div.comment-inline-form {
4163 4238 margin-top: 5px;
4164 4239 padding:2px 6px 8px 6px;
4165 4240
4166 4241 }
4167 4242
4168 4243 .comment-inline-form strong {
4169 4244 display: block;
4170 4245 margin-bottom: 15px;
4171 4246 }
4172 4247
4173 4248 .comment-inline-form textarea {
4174 4249 width: 100%;
4175 4250 height: 100px;
4176 4251 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4177 4252 }
4178 4253
4179 4254 form.comment-inline-form {
4180 4255 margin-top: 10px;
4181 4256 margin-left: 10px;
4182 4257 }
4183 4258
4184 4259 .comment-inline-form-submit {
4185 4260 margin-top: 5px;
4186 4261 margin-left: 525px;
4187 4262 }
4188 4263
4189 4264 .file-comments {
4190 4265 display: none;
4191 4266 }
4192 4267
4193 4268 .comment-inline-form .comment {
4194 4269 margin-left: 10px;
4195 4270 }
4196 4271
4197 4272 .comment-inline-form .comment-help{
4198 4273 padding: 0px 0px 2px 0px;
4199 4274 color: #666666;
4200 4275 font-size: 10px;
4201 4276 }
4202 4277
4203 4278 .comment-inline-form .comment-button{
4204 4279 padding-top:5px;
4205 4280 }
4206 4281
4207 4282 /** comment inline **/
4208 4283 .inline-comments {
4209 4284 padding:10px 20px;
4210 4285 }
4211 4286
4212 4287 .inline-comments div.rst-block {
4213 4288 clear:both;
4214 4289 overflow:hidden;
4215 4290 margin:0;
4216 4291 padding:0 20px 0px;
4217 4292 }
4218 4293 .inline-comments .comment {
4219 4294 border: 1px solid #ddd;
4220 4295 -webkit-border-radius: 4px;
4221 4296 -moz-border-radius: 4px;
4222 4297 border-radius: 4px;
4223 4298 margin: 3px 3px 5px 5px;
4224 4299 background-color: #FAFAFA;
4225 4300 }
4226 4301 .inline-comments .add-comment {
4227 4302 padding: 2px 4px 8px 5px;
4228 4303 }
4229 4304
4230 4305 .inline-comments .comment-wrapp{
4231 4306 padding:1px;
4232 4307 }
4233 4308 .inline-comments .comment .meta {
4234 4309 background: #f8f8f8;
4235 4310 padding: 4px;
4236 4311 border-bottom: 1px solid #ddd;
4237 4312 height: 20px;
4238 4313 }
4239 4314
4240 4315 .inline-comments .comment .meta img {
4241 4316 vertical-align: middle;
4242 4317 }
4243 4318
4244 4319 .inline-comments .comment .meta .user {
4245 4320 font-weight: bold;
4246 4321 float:left;
4247 4322 padding: 3px;
4248 4323 }
4249 4324
4250 4325 .inline-comments .comment .meta .date {
4251 4326 float:left;
4252 4327 padding: 3px;
4253 4328 }
4254 4329
4255 4330 .inline-comments .comment .text {
4256 4331 background-color: #FAFAFA;
4257 4332 }
4258 4333
4259 4334 .inline-comments .comments-number{
4260 4335 padding:0px 0px 10px 0px;
4261 4336 font-weight: bold;
4262 4337 color: #666;
4263 4338 font-size: 16px;
4264 4339 }
4265 4340 .inline-comments-button .add-comment{
4266 4341 margin:2px 0px 8px 5px !important
4267 4342 }
4268 4343
4269 4344
4270 4345 .notification-paginator{
4271 4346 padding: 0px 0px 4px 16px;
4272 4347 float: left;
4273 4348 }
4274 4349
4275 4350 .notifications{
4276 4351 border-radius: 4px 4px 4px 4px;
4277 4352 -webkit-border-radius: 4px;
4278 4353 -moz-border-radius: 4px;
4279 4354 float: right;
4280 4355 margin: 20px 0px 0px 0px;
4281 4356 position: absolute;
4282 4357 text-align: center;
4283 4358 width: 26px;
4284 4359 z-index: 1000;
4285 4360 }
4286 4361 .notifications a{
4287 4362 color:#888 !important;
4288 4363 display: block;
4289 4364 font-size: 10px;
4290 4365 background-color: #DEDEDE !important;
4291 4366 border-radius: 2px !important;
4292 4367 -webkit-border-radius: 2px !important;
4293 4368 -moz-border-radius: 2px !important;
4294 4369 }
4295 4370 .notifications a:hover{
4296 4371 text-decoration: none !important;
4297 4372 background-color: #EEEFFF !important;
4298 4373 }
4299 4374 .notification-header{
4300 4375 padding-top:6px;
4301 4376 }
4302 4377 .notification-header .desc{
4303 4378 font-size: 16px;
4304 4379 height: 24px;
4305 4380 float: left
4306 4381 }
4307 4382 .notification-list .container.unread{
4308 4383 background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
4309 4384 }
4310 4385 .notification-header .gravatar{
4311 4386 background: none repeat scroll 0 0 transparent;
4312 4387 padding: 0px 0px 0px 8px;
4313 4388 }
4314 4389 .notification-list .container .notification-header .desc{
4315 4390 font-weight: bold;
4316 4391 font-size: 17px;
4317 4392 }
4318 4393 .notification-table{
4319 4394 border: 1px solid #ccc;
4320 4395 -webkit-border-radius: 6px 6px 6px 6px;
4321 4396 -moz-border-radius: 6px 6px 6px 6px;
4322 4397 border-radius: 6px 6px 6px 6px;
4323 4398 clear: both;
4324 4399 margin: 0px 20px 0px 20px;
4325 4400 }
4326 4401 .notification-header .delete-notifications{
4327 4402 float: right;
4328 4403 padding-top: 8px;
4329 4404 cursor: pointer;
4330 4405 }
4331 4406 .notification-header .read-notifications{
4332 4407 float: right;
4333 4408 padding-top: 8px;
4334 4409 cursor: pointer;
4335 4410 }
4336 4411 .notification-subject{
4337 4412 clear:both;
4338 4413 border-bottom: 1px solid #eee;
4339 4414 padding:5px 0px 5px 38px;
4340 4415 }
4341 4416
4342 4417 .notification-body{
4343 4418 clear:both;
4344 4419 margin: 34px 2px 2px 8px
4345 4420 }
4346 4421
4347 4422 /****
4348 4423 PULL REQUESTS
4349 4424 *****/
4350 4425 .pullrequests_section_head {
4351 4426 padding:10px 10px 10px 0px;
4352 4427 font-size:16px;
4353 4428 font-weight: bold;
4354 4429 }
4355 4430
4356 4431 /****
4357 4432 PERMS
4358 4433 *****/
4359 4434 #perms .perms_section_head {
4360 4435 padding:10px 10px 10px 0px;
4361 4436 font-size:16px;
4362 4437 font-weight: bold;
4363 4438 }
4364 4439
4365 4440 #perms .perm_tag{
4366 4441 padding: 1px 3px 1px 3px;
4367 4442 font-size: 10px;
4368 4443 font-weight: bold;
4369 4444 text-transform: uppercase;
4370 4445 white-space: nowrap;
4371 4446 -webkit-border-radius: 3px;
4372 4447 -moz-border-radius: 3px;
4373 4448 border-radius: 3px;
4374 4449 }
4375 4450
4376 4451 #perms .perm_tag.admin{
4377 4452 background-color: #B94A48;
4378 4453 color: #ffffff;
4379 4454 }
4380 4455
4381 4456 #perms .perm_tag.write{
4382 4457 background-color: #B94A48;
4383 4458 color: #ffffff;
4384 4459 }
4385 4460
4386 4461 #perms .perm_tag.read{
4387 4462 background-color: #468847;
4388 4463 color: #ffffff;
4389 4464 }
4390 4465
4391 4466 #perms .perm_tag.none{
4392 4467 background-color: #bfbfbf;
4393 4468 color: #ffffff;
4394 4469 }
4395 4470
4396 4471 .perm-gravatar{
4397 4472 vertical-align:middle;
4398 4473 padding:2px;
4399 4474 }
4400 4475 .perm-gravatar-ac{
4401 4476 vertical-align:middle;
4402 4477 padding:2px;
4403 4478 width: 14px;
4404 4479 height: 14px;
4405 4480 }
4406 4481
4407 4482 /*****************************************************************************
4408 4483 DIFFS CSS
4409 4484 ******************************************************************************/
4410 4485
4411 4486 div.diffblock {
4412 4487 overflow: auto;
4413 4488 padding: 0px;
4414 4489 border: 1px solid #ccc;
4415 4490 background: #f8f8f8;
4416 4491 font-size: 100%;
4417 4492 line-height: 100%;
4418 4493 /* new */
4419 4494 line-height: 125%;
4420 4495 -webkit-border-radius: 6px 6px 0px 0px;
4421 4496 -moz-border-radius: 6px 6px 0px 0px;
4422 4497 border-radius: 6px 6px 0px 0px;
4423 4498 }
4424 4499 div.diffblock.margined{
4425 4500 margin: 0px 20px 0px 20px;
4426 4501 }
4427 4502 div.diffblock .code-header{
4428 4503 border-bottom: 1px solid #CCCCCC;
4429 4504 background: #EEEEEE;
4430 4505 padding:10px 0 10px 0;
4431 4506 height: 14px;
4432 4507 }
4433 4508 div.diffblock .code-header.cv{
4434 4509 height: 34px;
4435 4510 }
4436 4511 div.diffblock .code-header-title{
4437 4512 padding: 0px 0px 10px 5px !important;
4438 4513 margin: 0 !important;
4439 4514 }
4440 4515 div.diffblock .code-header .hash{
4441 4516 float: left;
4442 4517 padding: 2px 0 0 2px;
4443 4518 }
4444 4519 div.diffblock .code-header .date{
4445 4520 float:left;
4446 4521 text-transform: uppercase;
4447 4522 padding: 2px 0px 0px 2px;
4448 4523 }
4449 4524 div.diffblock .code-header div{
4450 4525 margin-left:4px;
4451 4526 font-weight: bold;
4452 4527 font-size: 14px;
4453 4528 }
4454 4529 div.diffblock .code-body{
4455 4530 background: #FFFFFF;
4456 4531 }
4457 4532 div.diffblock pre.raw{
4458 4533 background: #FFFFFF;
4459 4534 color:#000000;
4460 4535 }
4461 4536 table.code-difftable{
4462 4537 border-collapse: collapse;
4463 4538 width: 99%;
4464 4539 }
4465 4540 table.code-difftable td {
4466 4541 padding: 0 !important;
4467 4542 background: none !important;
4468 4543 border:0 !important;
4469 4544 vertical-align: none !important;
4470 4545 }
4471 4546 table.code-difftable .context{
4472 4547 background:none repeat scroll 0 0 #DDE7EF;
4473 4548 }
4474 4549 table.code-difftable .add{
4475 4550 background:none repeat scroll 0 0 #DDFFDD;
4476 4551 }
4477 4552 table.code-difftable .add ins{
4478 4553 background:none repeat scroll 0 0 #AAFFAA;
4479 4554 text-decoration:none;
4480 4555 }
4481 4556 table.code-difftable .del{
4482 4557 background:none repeat scroll 0 0 #FFDDDD;
4483 4558 }
4484 4559 table.code-difftable .del del{
4485 4560 background:none repeat scroll 0 0 #FFAAAA;
4486 4561 text-decoration:none;
4487 4562 }
4488 4563
4489 4564 /** LINE NUMBERS **/
4490 4565 table.code-difftable .lineno{
4491 4566
4492 4567 padding-left:2px;
4493 4568 padding-right:2px;
4494 4569 text-align:right;
4495 4570 width:32px;
4496 4571 -moz-user-select:none;
4497 4572 -webkit-user-select: none;
4498 4573 border-right: 1px solid #CCC !important;
4499 4574 border-left: 0px solid #CCC !important;
4500 4575 border-top: 0px solid #CCC !important;
4501 4576 border-bottom: none !important;
4502 4577 vertical-align: middle !important;
4503 4578
4504 4579 }
4505 4580 table.code-difftable .lineno.new {
4506 4581 }
4507 4582 table.code-difftable .lineno.old {
4508 4583 }
4509 4584 table.code-difftable .lineno a{
4510 4585 color:#747474 !important;
4511 4586 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4512 4587 letter-spacing:-1px;
4513 4588 text-align:right;
4514 4589 padding-right: 2px;
4515 4590 cursor: pointer;
4516 4591 display: block;
4517 4592 width: 32px;
4518 4593 }
4519 4594
4520 4595 table.code-difftable .lineno-inline{
4521 4596 background:none repeat scroll 0 0 #FFF !important;
4522 4597 padding-left:2px;
4523 4598 padding-right:2px;
4524 4599 text-align:right;
4525 4600 width:30px;
4526 4601 -moz-user-select:none;
4527 4602 -webkit-user-select: none;
4528 4603 }
4529 4604
4530 4605 /** CODE **/
4531 4606 table.code-difftable .code {
4532 4607 display: block;
4533 4608 width: 100%;
4534 4609 }
4535 4610 table.code-difftable .code td{
4536 4611 margin:0;
4537 4612 padding:0;
4538 4613 }
4539 4614 table.code-difftable .code pre{
4540 4615 margin:0;
4541 4616 padding:0;
4542 4617 height: 17px;
4543 4618 line-height: 17px;
4544 4619 }
4545 4620
4546 4621
4547 4622 .diffblock.margined.comm .line .code:hover{
4548 4623 background-color:#FFFFCC !important;
4549 4624 cursor: pointer !important;
4550 4625 background-image:url("../images/icons/comment_add.png") !important;
4551 4626 background-repeat:no-repeat !important;
4552 4627 background-position: right !important;
4553 4628 background-position: 0% 50% !important;
4554 4629 }
4555 4630 .diffblock.margined.comm .line .code.no-comment:hover{
4556 4631 background-image: none !important;
4557 4632 cursor: auto !important;
4558 4633 background-color: inherit !important;
4559 4634
4560 4635 }
@@ -1,249 +1,306 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Settings administration')} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Settings')}
10 10 </%def>
11 11
12 12 <%def name="page_nav()">
13 13 ${self.menu('admin')}
14 14 </%def>
15 15
16 16 <%def name="main()">
17 17 <div class="box">
18 18 <!-- box / title -->
19 19 <div class="title">
20 20 ${self.breadcrumbs()}
21 21 </div>
22 22 <!-- end box / title -->
23 23
24 24 <h3>${_('Remap and rescan repositories')}</h3>
25 25 ${h.form(url('admin_setting', setting_id='mapping'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label label-checkbox">
32 32 <label for="destroy">${_('rescan option')}:</label>
33 33 </div>
34 34 <div class="checkboxes">
35 35 <div class="checkbox">
36 36 ${h.checkbox('destroy',True)}
37 37 <label for="destroy">
38 38 <span class="tooltip" title="${h.tooltip(_('In case a repository was deleted from filesystem and there are leftovers in the database check this option to scan obsolete data in database and remove it.'))}">
39 39 ${_('destroy old data')}</span> </label>
40 40 </div>
41 41 <span class="help-block">${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}</span>
42 42 </div>
43 43 </div>
44 44
45 45 <div class="buttons">
46 46 ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
47 47 </div>
48 48 </div>
49 49 </div>
50 50 ${h.end_form()}
51 51
52 52 <h3>${_('Whoosh indexing')}</h3>
53 53 ${h.form(url('admin_setting', setting_id='whoosh'),method='put')}
54 54 <div class="form">
55 55 <!-- fields -->
56 56
57 57 <div class="fields">
58 58 <div class="field">
59 59 <div class="label label-checkbox">
60 60 <label>${_('index build option')}:</label>
61 61 </div>
62 62 <div class="checkboxes">
63 63 <div class="checkbox">
64 64 ${h.checkbox('full_index',True)}
65 65 <label for="full_index">${_('build from scratch')}</label>
66 66 </div>
67 67 </div>
68 68 </div>
69 69
70 70 <div class="buttons">
71 71 ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
72 72 </div>
73 73 </div>
74 74 </div>
75 75 ${h.end_form()}
76 76
77 77 <h3>${_('Global application settings')}</h3>
78 78 ${h.form(url('admin_setting', setting_id='global'),method='put')}
79 79 <div class="form">
80 80 <!-- fields -->
81 81
82 82 <div class="fields">
83 83
84 84 <div class="field">
85 85 <div class="label">
86 86 <label for="rhodecode_title">${_('Application name')}:</label>
87 87 </div>
88 88 <div class="input">
89 89 ${h.text('rhodecode_title',size=30)}
90 90 </div>
91 91 </div>
92 92
93 93 <div class="field">
94 94 <div class="label">
95 95 <label for="rhodecode_realm">${_('Realm text')}:</label>
96 96 </div>
97 97 <div class="input">
98 98 ${h.text('rhodecode_realm',size=30)}
99 99 </div>
100 100 </div>
101 101
102 102 <div class="field">
103 103 <div class="label">
104 104 <label for="rhodecode_ga_code">${_('GA code')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 ${h.text('rhodecode_ga_code',size=30)}
108 108 </div>
109 109 </div>
110 110
111 111 <div class="buttons">
112 112 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
113 113 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
114 114 </div>
115 115 </div>
116 116 </div>
117 117 ${h.end_form()}
118 118
119 <h3>${_('Visualisation settings')}</h3>
120 ${h.form(url('admin_setting', setting_id='visual'),method='put')}
121 <div class="form">
122 <!-- fields -->
123
124 <div class="fields">
125
126 <div class="field">
127 <div class="label label-checkbox">
128 <label>${_('Icons')}:</label>
129 </div>
130 <div class="checkboxes">
131 <div class="checkbox">
132 ${h.checkbox('rhodecode_show_public_icon','True')}
133 <label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
134 </div>
135 <div class="checkbox">
136 ${h.checkbox('rhodecode_show_private_icon','True')}
137 <label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
138 </div>
139 </div>
140 </div>
141
142 <div class="field">
143 <div class="label label-checkbox">
144 <label>${_('Meta-Tagging')}:</label>
145 </div>
146 <div class="checkboxes">
147 <div class="checkbox">
148 ${h.checkbox('rhodecode_stylify_metatags','True')}
149 <label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
150 </div>
151 <div style="padding-left: 20px;">
152 <ul> <!-- Fix style here -->
153 <li>[featured] <span class="metatag" tag="featured">featured</span></li>
154 <li>[stale] <span class="metatag" tag="stale">stale</span></li>
155 <li>[dead] <span class="metatag" tag="dead">dead</span></li>
156 <li>[lang =&gt; lang] <span class="metatag" tag="lang" >lang</span></li>
157 <li>[license =&gt; License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
158 <li>[requires =&gt; Repo] <span class="metatag" tag="requires" >requires =&gt; <a href="#" >Repo</a></span></li>
159 <li>[recommends =&gt; Repo] <span class="metatag" tag="recommends" >recommends =&gt; <a href="#" >Repo</a></span></li>
160 <li>[see =&gt; URI] <span class="metatag" tag="see">see =&gt; <a href="#">URI</a> </span></li>
161 </ul>
162 </div>
163 </div>
164 </div>
165
166 <div class="buttons">
167 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
168 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
169 </div>
170
171 </div>
172 </div>
173 ${h.end_form()}
174
175
119 176 <h3>${_('VCS settings')}</h3>
120 177 ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
121 178 <div class="form">
122 179 <!-- fields -->
123 180
124 181 <div class="fields">
125 182
126 183 <div class="field">
127 184 <div class="label label-checkbox">
128 185 <label>${_('Web')}:</label>
129 186 </div>
130 187 <div class="checkboxes">
131 188 <div class="checkbox">
132 189 ${h.checkbox('web_push_ssl','true')}
133 190 <label for="web_push_ssl">${_('require ssl for vcs operations')}</label>
134 191 </div>
135 192 <span class="help-block">${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}</span>
136 193 </div>
137 194 </div>
138 195
139 196 <div class="field">
140 197 <div class="label label-checkbox">
141 198 <label>${_('Hooks')}:</label>
142 199 </div>
143 200 <div class="checkboxes">
144 201 <div class="checkbox">
145 202 ${h.checkbox('hooks_changegroup_update','True')}
146 203 <label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
147 204 </div>
148 205 <div class="checkbox">
149 206 ${h.checkbox('hooks_changegroup_repo_size','True')}
150 207 <label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
151 208 </div>
152 209 <div class="checkbox">
153 210 ${h.checkbox('hooks_changegroup_push_logger','True')}
154 211 <label for="hooks_changegroup_push_logger">${_('Log user push commands')}</label>
155 212 </div>
156 213 <div class="checkbox">
157 214 ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
158 215 <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
159 216 </div>
160 217 </div>
161 218 <div class="input" style="margin-top:10px">
162 219 ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
163 220 </div>
164 221 </div>
165 222 <div class="field">
166 223 <div class="label">
167 224 <label for="paths_root_path">${_('Repositories location')}:</label>
168 225 </div>
169 226 <div class="input">
170 227 ${h.text('paths_root_path',size=30,readonly="readonly")}
171 228 <span id="path_unlock" class="tooltip"
172 229 title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
173 230 ${_('unlock')}</span>
174 231 <span class="help-block">${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}</span>
175 232 </div>
176 233 </div>
177 234
178 235 <div class="buttons">
179 236 ${h.submit('save',_('Save settings'),class_="ui-btn large")}
180 237 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
181 238 </div>
182 239 </div>
183 240 </div>
184 241 ${h.end_form()}
185 242
186 243 <script type="text/javascript">
187 244 YAHOO.util.Event.onDOMReady(function(){
188 245 YAHOO.util.Event.addListener('path_unlock','click',function(){
189 246 YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
190 247 });
191 248 });
192 249 </script>
193 250
194 251 <h3>${_('Test Email')}</h3>
195 252 ${h.form(url('admin_setting', setting_id='email'),method='put')}
196 253 <div class="form">
197 254 <!-- fields -->
198 255
199 256 <div class="fields">
200 257 <div class="field">
201 258 <div class="label">
202 259 <label for="test_email">${_('Email to')}:</label>
203 260 </div>
204 261 <div class="input">
205 262 ${h.text('test_email',size=30)}
206 263 </div>
207 264 </div>
208 265
209 266 <div class="buttons">
210 267 ${h.submit('send',_('Send'),class_="ui-btn large")}
211 268 </div>
212 269 </div>
213 270 </div>
214 271 ${h.end_form()}
215 272
216 273 <h3>${_('System Info and Packages')}</h3>
217 274 <div class="form">
218 275 <div>
219 276 <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
220 277 </div>
221 278 <div id="expand_modules_table" style="display:none">
222 279 <h5>Python - ${c.py_version}</h5>
223 280 <h5>System - ${c.platform}</h5>
224 281
225 282 <table class="table" style="margin:0px 0px 0px 20px">
226 283 <colgroup>
227 284 <col style="width:220px">
228 285 </colgroup>
229 286 <tbody>
230 287 %for key, value in c.modules:
231 288 <tr>
232 289 <th style="text-align: right;padding-right:5px;">${key}</th>
233 290 <td>${value}</td>
234 291 </tr>
235 292 %endfor
236 293 </tbody>
237 294 </table>
238 295 </div>
239 296 </div>
240 297
241 298 <script type="text/javascript">
242 299 YUE.on('expand_modules','click',function(e){
243 300 YUD.setStyle('expand_modules_table','display','');
244 301 YUD.setStyle('expand_modules','display','none');
245 302 })
246 303 </script>
247 304
248 305 </div>
249 306 </%def>
@@ -1,111 +1,110 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4 4
5 5 <%def name="repo_actions(repo_name)">
6 6 ${h.form(h.url('repo', repo_name=repo_name),method='delete')}
7 7 ${h.submit('remove_%s' % repo_name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
8 8 ${h.end_form()}
9 9 </%def>
10 10
11 11 <%def name="quick_menu(repo_name)">
12 12 <ul class="menu_items hidden">
13 13 <li style="border-top:1px solid #003367;margin-left:18px;padding-left:-99px"></li>
14 14 <li>
15 15 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
16 16 <span class="icon">
17 17 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
18 18 </span>
19 19 <span>${_('Summary')}</span>
20 20 </a>
21 21 </li>
22 22 <li>
23 23 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
24 24 <span class="icon">
25 25 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
26 26 </span>
27 27 <span>${_('Changelog')}</span>
28 28 </a>
29 29 </li>
30 30 <li>
31 31 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
32 32 <span class="icon">
33 33 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
34 34 </span>
35 35 <span>${_('Files')}</span>
36 36 </a>
37 37 </li>
38 38 <li>
39 39 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
40 40 <span class="icon">
41 41 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Fork')}" />
42 42 </span>
43 43 <span>${_('Fork')}</span>
44 44 </a>
45 45 </li>
46 46 </ul>
47 47 </%def>
48 48
49 49 <%def name="repo_name(name,rtype,private,fork_of,short_name=False, admin=False)">
50 50 <%
51 51 def get_name(name,short_name=short_name):
52 52 if short_name:
53 53 return name.split('/')[-1]
54 54 else:
55 55 return name
56 56 %>
57 57 <div style="white-space: nowrap">
58 58 ##TYPE OF REPO
59 59 %if h.is_hg(rtype):
60 60 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
61 61 %elif h.is_git(rtype):
62 62 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
63 63 %endif
64 64
65 65 ##PRIVATE/PUBLIC
66 %if private:
66 %if private and c.visual.show_private_icon:
67 67 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
68 %else:
68 %elif not private and c.visual.show_public_icon:
69 69 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
70 70 %endif
71 71
72 72 ##NAME
73 73 %if admin:
74 74 ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")}
75 75 %else:
76 76 ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")}
77 77 %endif
78 78 %if fork_of:
79 79 <a href="${h.url('summary_home',repo_name=fork_of)}">
80 80 <img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
81 81 %endif
82 82 </div>
83 83 </%def>
84 84
85 85
86 86
87 87 <%def name="revision(name,rev,tip,author,last_msg)">
88 88 <div>
89 89 %if rev >= 0:
90 90 <pre><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></pre>
91 91 %else:
92 92 ${_('No changesets yet')}
93 93 %endif
94 94 </div>
95 95 </%def>
96 96
97 97 <%def name="user_gravatar(email, size=24)">
98 98 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(email, size)}"/> </div>
99 99 </%def>
100 100
101 101 <%def name="user_actions(user_id, username)">
102 102 ${h.form(h.url('delete_user', id=user_id),method='delete')}
103 103 ${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id,
104 104 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
105 105 ${h.end_form()}
106 106 </%def>
107 107
108 108 <%def name="user_name(user_id, username)">
109 109 ${h.link_to(username,h.url('edit_user', id=user_id))}
110 110 </%def>
111
@@ -1,201 +1,209 b''
1 1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 7 </h5>
8 8 %if c.rhodecode_user.username != 'default':
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 11 <li>
12 12 %if c.group:
13 13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 14 %else:
15 15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 16 %endif
17 17 </li>
18 18 </ul>
19 19 %endif
20 20 %endif
21 21 </div>
22 22 <!-- end box / title -->
23 23 <div class="table">
24 24 % if c.groups:
25 25 <div id='groups_list_wrap' class="yui-skin-sam">
26 26 <table id="groups_list">
27 27 <thead>
28 28 <tr>
29 29 <th class="left"><a href="#">${_('Group name')}</a></th>
30 30 <th class="left"><a href="#">${_('Description')}</a></th>
31 31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 32 </tr>
33 33 </thead>
34 34
35 35 ## REPO GROUPS
36 36 % for gr in c.groups:
37 37 <tr>
38 38 <td>
39 39 <div style="white-space: nowrap">
40 40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 42 </div>
43 43 </td>
44 %if c.visual.stylify_metatags:
45 <td>${h.desc_stylize(gr.group_description)}</td>
46 %else:
44 47 <td>${gr.group_description}</td>
48 %endif
45 49 ## this is commented out since for multi nested repos can be HEAVY!
46 50 ## in number of executed queries during traversing uncomment at will
47 51 ##<td><b>${gr.repositories_recursive_count}</b></td>
48 52 </tr>
49 53 % endfor
50 54
51 55 </table>
52 56 </div>
53 57 <div style="height: 20px"></div>
54 58 % endif
55 59 <div id="welcome" style="display:none;text-align:center">
56 60 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
57 61 </div>
58 62 <div id='repos_list_wrap' class="yui-skin-sam">
59 63 <%cnt=0%>
60 64 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
61 65
62 66 <table id="repos_list">
63 67 <thead>
64 68 <tr>
65 69 <th class="left"></th>
66 70 <th class="left">${_('Name')}</th>
67 71 <th class="left">${_('Description')}</th>
68 72 <th class="left">${_('Last change')}</th>
69 73 <th class="left">${_('Tip')}</th>
70 74 <th class="left">${_('Owner')}</th>
71 75 <th class="left">${_('RSS')}</th>
72 76 <th class="left">${_('Atom')}</th>
73 77 </tr>
74 78 </thead>
75 79 <tbody>
76 80 %for cnt,repo in enumerate(c.repos_list):
77 81 <tr class="parity${(cnt+1)%2}">
78 82 ##QUICK MENU
79 83 <td class="quick_repo_menu">
80 84 ${dt.quick_menu(repo['name'])}
81 85 </td>
82 86 ##REPO NAME AND ICONS
83 87 <td class="reponame">
84 88 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
85 89 </td>
86 90 ##DESCRIPTION
87 91 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
92 %if c.visual.stylify_metatags:
93 ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
94 %else:
88 95 ${h.truncate(repo['description'],60)}</span>
96 %endif
89 97 </td>
90 98 ##LAST CHANGE DATE
91 99 <td>
92 100 <span class="tooltip" date="${repo['last_change']}" title="${h.tooltip(h.fmt_date(repo['last_change']))}">${h.age(repo['last_change'])}</span>
93 101 </td>
94 102 ##LAST REVISION
95 103 <td>
96 104 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
97 105 </td>
98 106 ##
99 107 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
100 108 <td>
101 109 %if c.rhodecode_user.username != 'default':
102 110 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
103 111 %else:
104 112 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
105 113 %endif:
106 114 </td>
107 115 <td>
108 116 %if c.rhodecode_user.username != 'default':
109 117 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
110 118 %else:
111 119 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
112 120 %endif:
113 121 </td>
114 122 </tr>
115 123 %endfor
116 124 </tbody>
117 125 </table>
118 126 </div>
119 127 </div>
120 128 </div>
121 129 <script>
122 130 YUD.get('repo_count').innerHTML = ${cnt+1 if cnt else 0};
123 131 var func = function(node){
124 132 return node.parentNode.parentNode.parentNode.parentNode;
125 133 }
126 134
127 135
128 136 // groups table sorting
129 137 var myColumnDefs = [
130 138 {key:"name",label:"${_('Group Name')}",sortable:true,
131 139 sortOptions: { sortFunction: groupNameSort }},
132 140 {key:"desc",label:"${_('Description')}",sortable:true},
133 141 ];
134 142
135 143 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
136 144
137 145 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
138 146 myDataSource.responseSchema = {
139 147 fields: [
140 148 {key:"name"},
141 149 {key:"desc"},
142 150 ]
143 151 };
144 152
145 153 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
146 154 {
147 155 sortedBy:{key:"name",dir:"asc"},
148 156 MSG_SORTASC:"${_('Click to sort ascending')}",
149 157 MSG_SORTDESC:"${_('Click to sort descending')}"
150 158 }
151 159 );
152 160
153 161 // main table sorting
154 162 var myColumnDefs = [
155 163 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
156 164 {key:"name",label:"${_('Name')}",sortable:true,
157 165 sortOptions: { sortFunction: nameSort }},
158 166 {key:"desc",label:"${_('Description')}",sortable:true},
159 167 {key:"last_change",label:"${_('Last Change')}",sortable:true,
160 168 sortOptions: { sortFunction: ageSort }},
161 169 {key:"tip",label:"${_('Tip')}",sortable:true,
162 170 sortOptions: { sortFunction: revisionSort }},
163 171 {key:"owner",label:"${_('Owner')}",sortable:true},
164 172 {key:"rss",label:"",sortable:false},
165 173 {key:"atom",label:"",sortable:false},
166 174 ];
167 175
168 176 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
169 177
170 178 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
171 179
172 180 myDataSource.responseSchema = {
173 181 fields: [
174 182 {key:"menu"},
175 183 {key:"name"},
176 184 {key:"desc"},
177 185 {key:"last_change"},
178 186 {key:"tip"},
179 187 {key:"owner"},
180 188 {key:"rss"},
181 189 {key:"atom"},
182 190 ]
183 191 };
184 192
185 193 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
186 194 {
187 195 sortedBy:{key:"name",dir:"asc"},
188 196 MSG_SORTASC:"${_('Click to sort ascending')}",
189 197 MSG_SORTDESC:"${_('Click to sort descending')}",
190 198 MSG_EMPTY:"${_('No records found.')}",
191 199 MSG_ERROR:"${_('Data error.')}",
192 200 MSG_LOADING:"${_('Loading...')}",
193 201 }
194 202 );
195 203 myDataTable.subscribe('postRenderEvent',function(oArgs) {
196 204 tooltip_activate();
197 205 quick_repo_menu();
198 206 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
199 207 });
200 208
201 209 </script>
@@ -1,229 +1,229 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3 <%def name="title()">
4 4 ${_('Journal')} - ${c.rhodecode_name}
5 5 </%def>
6 6 <%def name="breadcrumbs()">
7 7 ${c.rhodecode_name}
8 8 </%def>
9 9 <%def name="page_nav()">
10 10 ${self.menu('home')}
11 11 </%def>
12 12 <%def name="head_extra()">
13 13 <link href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
14 14 <link href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
15 15 </%def>
16 16 <%def name="main()">
17 17
18 18 <div class="box box-left">
19 19 <!-- box / title -->
20 20 <div class="title">
21 21 <h5>${_('Journal')}</h5>
22 22 <ul class="links">
23 23 <li>
24 24 <span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
25 25 </li>
26 26 <li>
27 27 <span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
28 28 </li>
29 29 <li>
30 30 <span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
31 31 </li>
32 32 </ul>
33 33 </div>
34 34 <div id="journal">${c.journal_data}</div>
35 35 </div>
36 36 <div class="box box-right">
37 37 <!-- box / title -->
38 38 <div class="title">
39 39 <h5>
40 40 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
41 41 <a id="show_my" class="link-white" href="#my">${_('My repos')}</a> / <a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a>
42 42 </h5>
43 43 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
44 44 <ul class="links">
45 45 <li>
46 46 <span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
47 47 </li>
48 48 </ul>
49 49 %endif
50 50 </div>
51 51 <!-- end box / title -->
52 52 <div id="my" class="table">
53 53 %if c.user_repos:
54 54 <div id='repos_list_wrap' class="yui-skin-sam">
55 55 <table id="repos_list">
56 56 <thead>
57 57 <tr>
58 58 <th></th>
59 59 <th class="left">${_('Name')}</th>
60 60 <th class="left">${_('Revision')}</th>
61 61 <th class="left">${_('Action')}</th>
62 62 <th class="left">${_('Action')}</th>
63 63 </thead>
64 64 <tbody>
65 65 <%namespace name="dt" file="/data_table/_dt_elements.html"/>
66 66 %for repo in c.user_repos:
67 67 <tr>
68 68 ##QUICK MENU
69 69 <td class="quick_repo_menu">
70 70 ${dt.quick_menu(repo['name'])}
71 71 </td>
72 72 ##REPO NAME AND ICONS
73 73 <td class="reponame">
74 74 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))}
75 75 </td>
76 76 ##LAST REVISION
77 77 <td>
78 78 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
79 79 </td>
80 80 ##
81 81 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url('/images/icons/application_form_edit.png')}"/></a></td>
82 82 <td>
83 83 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
84 84 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
85 85 ${h.end_form()}
86 86 </td>
87 87 </tr>
88 88 %endfor
89 89 </tbody>
90 90 </table>
91 91 </div>
92 92 %else:
93 93 <div style="padding:5px 0px 10px 0px;">
94 94 ${_('No repositories yet')}
95 95 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
96 96 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'),class_="ui-btn")}
97 97 %endif
98 98 </div>
99 99 %endif
100 100 </div>
101 101
102 102 <div id="watched" class="table" style="display:none">
103 103 %if c.following:
104 104 <table>
105 105 <thead>
106 106 <tr>
107 107 <th class="left">${_('Name')}</th>
108 108 </thead>
109 109 <tbody>
110 110 %for entry in c.following:
111 111 <tr>
112 112 <td>
113 113 %if entry.follows_user_id:
114 114 <img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
115 115 ${entry.follows_user.full_contact}
116 116 %endif
117 117
118 118 %if entry.follows_repo_id:
119 119 <div style="float:right;padding-right:5px">
120 120 <span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
121 121 onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
122 122 </span>
123 123 </div>
124 124
125 125 %if h.is_hg(entry.follows_repository):
126 126 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
127 127 %elif h.is_git(entry.follows_repository):
128 128 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
129 129 %endif
130 130
131 %if entry.follows_repository.private:
131 %if entry.follows_repository.private and c.visual.show_private_icon:
132 132 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
133 %else:
133 %elif not entry.follows_repository.private and c.visual.show_public_icon:
134 134 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
135 135 %endif
136 136 <span class="watched_repo">
137 137 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
138 138 </span>
139 139 %endif
140 140 </td>
141 141 </tr>
142 142 %endfor
143 143 </tbody>
144 144 </table>
145 145 %else:
146 146 <div style="padding:5px 0px 10px 0px;">
147 147 ${_('You are not following any users or repositories')}
148 148 </div>
149 149 %endif
150 150 </div>
151 151 </div>
152 152
153 153 <script type="text/javascript">
154 154
155 155 YUE.on('show_my','click',function(e){
156 156 YUD.setStyle('watched','display','none');
157 157 YUD.setStyle('my','display','');
158 158 var nodes = YUQ('#my tr td a.repo_name');
159 159 var target = 'q_filter';
160 160 var func = function(node){
161 161 return node.parentNode.parentNode.parentNode.parentNode;
162 162 }
163 163 q_filter(target,nodes,func);
164 164 YUE.preventDefault(e);
165 165 })
166 166 YUE.on('show_watched','click',function(e){
167 167 YUD.setStyle('my','display','none');
168 168 YUD.setStyle('watched','display','');
169 169 var nodes = YUQ('#watched .watched_repo a');
170 170 var target = 'q_filter';
171 171 var func = function(node){
172 172 return node.parentNode.parentNode;
173 173 }
174 174 q_filter(target,nodes,func);
175 175 YUE.preventDefault(e);
176 176 })
177 177 YUE.on('refresh','click',function(e){
178 178 ypjax(e.currentTarget.href,"journal",function(){show_more_event();tooltip_activate();});
179 179 YUE.preventDefault(e);
180 180 });
181 181
182 182
183 183 // main table sorting
184 184 var myColumnDefs = [
185 185 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
186 186 {key:"name",label:"${_('Name')}",sortable:true,
187 187 sortOptions: { sortFunction: nameSort }},
188 188 {key:"tip",label:"${_('Tip')}",sortable:true,
189 189 sortOptions: { sortFunction: revisionSort }},
190 190 {key:"action1",label:"",sortable:false},
191 191 {key:"action2",label:"",sortable:false},
192 192 ];
193 193
194 194 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
195 195
196 196 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
197 197
198 198 myDataSource.responseSchema = {
199 199 fields: [
200 200 {key:"menu"},
201 201 {key:"name"},
202 202 {key:"tip"},
203 203 {key:"action1"},
204 204 {key:"action2"}
205 205 ]
206 206 };
207 207
208 208 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
209 209 {
210 210 sortedBy:{key:"name",dir:"asc"},
211 211 MSG_SORTASC:"${_('Click to sort ascending')}",
212 212 MSG_SORTDESC:"${_('Click to sort descending')}",
213 213 MSG_EMPTY:"${_('No records found.')}",
214 214 MSG_ERROR:"${_('Data error.')}",
215 215 MSG_LOADING:"${_('Loading...')}",
216 216 }
217 217 );
218 218 myDataTable.subscribe('postRenderEvent',function(oArgs) {
219 219 tooltip_activate();
220 220 quick_repo_menu();
221 221 var func = function(node){
222 222 return node.parentNode.parentNode.parentNode.parentNode;
223 223 }
224 224 q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
225 225 });
226 226
227 227
228 228 </script>
229 229 </%def>
@@ -1,20 +1,20 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <li class="qfilter_rs">
4 4 <input type="text" style="border:0;width:100%" value="${_('quick filter...')}" name="filter" id="q_filter_rs" />
5 5 </li>
6 6
7 7 %for repo in c.repos_list:
8 8
9 %if repo['dbrepo']['private']:
9 %if repo['dbrepo']['private'] and c.visual.show_private_icon:
10 10 <li>
11 11 <img src="${h.url('/images/icons/lock.png')}" alt="${_('Private repository')}" class="repo_switcher_type"/>
12 12 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
13 13 </li>
14 %else:
14 %elif not repo['dbrepo']['private'] and c.visual.show_public_icon:
15 15 <li>
16 16 <img src="${h.url('/images/icons/lock_open.png')}" alt="${_('Public repository')}" class="repo_switcher_type" />
17 17 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
18 18 </li>
19 19 %endif
20 20 %endfor
@@ -1,706 +1,710 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Summary') % c.repo_name} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="head_extra()">
20 20 <link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s ATOM feed') % c.repo_name}" type="application/atom+xml" />
21 21 <link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s RSS feed') % c.repo_name}" type="application/rss+xml" />
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <%
26 26 summary = lambda n:{False:'summary-short'}.get(n)
27 27 %>
28 28 %if c.show_stats:
29 29 <div class="box box-left">
30 30 %else:
31 31 <div class="box">
32 32 %endif
33 33 <!-- box / title -->
34 34 <div class="title">
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37 <!-- end box / title -->
38 38 <div class="form">
39 39 <div id="summary" class="fields">
40 40
41 41 <div class="field">
42 42 <div class="label-summary">
43 43 <label>${_('Name')}:</label>
44 44 </div>
45 45 <div class="input ${summary(c.show_stats)}">
46 46 <div style="float:right;padding:5px 0px 0px 5px">
47 47 %if c.rhodecode_user.username != 'default':
48 48 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
49 49 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
50 50 %else:
51 51 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
52 52 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
53 53 %endif
54 54 </div>
55 55 %if c.rhodecode_user.username != 'default':
56 56 %if c.following:
57 57 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
58 58 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
59 59 </span>
60 60 %else:
61 61 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
62 62 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
63 63 </span>
64 64 %endif
65 65 %endif:
66 66 ##REPO TYPE
67 67 %if h.is_hg(c.dbrepo):
68 68 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
69 69 %endif
70 70 %if h.is_git(c.dbrepo):
71 71 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
72 72 %endif
73 73
74 74 ##PUBLIC/PRIVATE
75 75 %if c.dbrepo.private:
76 76 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
77 77 %else:
78 78 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
79 79 %endif
80 80
81 81 ##REPO NAME
82 82 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
83 83
84 84 ##FORK
85 85 %if c.dbrepo.fork:
86 86 <div style="margin-top:5px;clear:both"">
87 87 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
88 88 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
89 89 </a>
90 90 </div>
91 91 %endif
92 92 ##REMOTE
93 93 %if c.dbrepo.clone_uri:
94 94 <div style="margin-top:5px;clear:both">
95 95 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
96 96 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
97 97 </a>
98 98 </div>
99 99 %endif
100 100 </div>
101 101 </div>
102 102
103 103 <div class="field">
104 104 <div class="label-summary">
105 105 <label>${_('Description')}:</label>
106 106 </div>
107 %if c.visual.stylify_metatags:
108 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
109 %else:
107 110 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
111 %endif
108 112 </div>
109 113
110 114 <div class="field">
111 115 <div class="label-summary">
112 116 <label>${_('Contact')}:</label>
113 117 </div>
114 118 <div class="input ${summary(c.show_stats)}">
115 119 <div class="gravatar">
116 120 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
117 121 </div>
118 122 ${_('Username')}: ${c.dbrepo.user.username}<br/>
119 123 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
120 124 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
121 125 </div>
122 126 </div>
123 127
124 128 <div class="field">
125 129 <div class="label-summary">
126 130 <label>${_('Clone url')}:</label>
127 131 </div>
128 132 <div class="input ${summary(c.show_stats)}">
129 133 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
130 134 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
131 135 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
132 136 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
133 137 </div>
134 138 </div>
135 139
136 140 <div class="field">
137 141 <div class="label-summary">
138 142 <label>${_('Trending files')}:</label>
139 143 </div>
140 144 <div class="input ${summary(c.show_stats)}">
141 145 %if c.show_stats:
142 146 <div id="lang_stats"></div>
143 147 %else:
144 148 ${_('Statistics are disabled for this repository')}
145 149 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
146 150 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
147 151 %endif
148 152 %endif
149 153 </div>
150 154 </div>
151 155
152 156 <div class="field">
153 157 <div class="label-summary">
154 158 <label>${_('Download')}:</label>
155 159 </div>
156 160 <div class="input ${summary(c.show_stats)}">
157 161 %if len(c.rhodecode_repo.revisions) == 0:
158 162 ${_('There are no downloads yet')}
159 163 %elif c.enable_downloads is False:
160 164 ${_('Downloads are disabled for this repository')}
161 165 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
162 166 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
163 167 %endif
164 168 %else:
165 169 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
166 170 <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
167 171 <span style="vertical-align: bottom">
168 172 <input id="archive_subrepos" type="checkbox" name="subrepos" />
169 173 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
170 174 </span>
171 175 %endif
172 176 </div>
173 177 </div>
174 178 </div>
175 179 </div>
176 180 </div>
177 181
178 182 %if c.show_stats:
179 183 <div class="box box-right" style="min-height:455px">
180 184 <!-- box / title -->
181 185 <div class="title">
182 186 <h5>${_('Commit activity by day / author')}</h5>
183 187 </div>
184 188
185 189 <div class="graph">
186 190 <div style="padding:0 10px 10px 17px;">
187 191 %if c.no_data:
188 192 ${c.no_data_msg}
189 193 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
190 194 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
191 195 %endif
192 196 %else:
193 197 ${_('Stats gathered: ')} ${c.stats_percentage}%
194 198 %endif
195 199 </div>
196 200 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
197 201 <div style="clear: both;height: 10px"></div>
198 202 <div id="overview" style="width:450px;height:100px;float:left"></div>
199 203
200 204 <div id="legend_data" style="clear:both;margin-top:10px;">
201 205 <div id="legend_container"></div>
202 206 <div id="legend_choices">
203 207 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
204 208 </div>
205 209 </div>
206 210 </div>
207 211 </div>
208 212 %endif
209 213
210 214 <div class="box">
211 215 <div class="title">
212 216 <div class="breadcrumbs">
213 217 %if c.repo_changesets:
214 218 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
215 219 %else:
216 220 ${_('Quick start')}
217 221 %endif
218 222 </div>
219 223 </div>
220 224 <div class="table">
221 225 <div id="shortlog_data">
222 226 <%include file='../shortlog/shortlog_data.html'/>
223 227 </div>
224 228 </div>
225 229 </div>
226 230
227 231 %if c.readme_data:
228 232 <div id="readme" class="box header-pos-fix" style="background-color: #FAFAFA">
229 233 <div id="readme" class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
230 234 <div class="breadcrumbs">
231 235 <a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a>
232 236 <a class="permalink" href="#readme" title="${_('Permalink to this readme')}">&para;</a>
233 237 </div>
234 238 </div>
235 239 <div id="readme" class="readme">
236 240 <div class="readme_box">
237 241 ${c.readme_data|n}
238 242 </div>
239 243 </div>
240 244 </div>
241 245 %endif
242 246
243 247 <script type="text/javascript">
244 248 var clone_url = 'clone_url';
245 249 YUE.on(clone_url,'click',function(e){
246 250 if(YUD.hasClass(clone_url,'selected')){
247 251 return
248 252 }
249 253 else{
250 254 YUD.addClass(clone_url,'selected');
251 255 YUD.get(clone_url).select();
252 256 }
253 257 })
254 258
255 259 YUE.on('clone_by_name','click',function(e){
256 260 // show url by name and hide name button
257 261 YUD.setStyle('clone_url','display','');
258 262 YUD.setStyle('clone_by_name','display','none');
259 263
260 264 // hide url by id and show name button
261 265 YUD.setStyle('clone_by_id','display','');
262 266 YUD.setStyle('clone_url_id','display','none');
263 267
264 268 })
265 269 YUE.on('clone_by_id','click',function(e){
266 270
267 271 // show url by id and hide id button
268 272 YUD.setStyle('clone_by_id','display','none');
269 273 YUD.setStyle('clone_url_id','display','');
270 274
271 275 // hide url by name and show id button
272 276 YUD.setStyle('clone_by_name','display','');
273 277 YUD.setStyle('clone_url','display','none');
274 278 })
275 279
276 280
277 281 var tmpl_links = {};
278 282 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
279 283 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
280 284 %endfor
281 285
282 286 YUE.on(['download_options','archive_subrepos'],'change',function(e){
283 287 var sm = YUD.get('download_options');
284 288 var new_cs = sm.options[sm.selectedIndex];
285 289
286 290 for(k in tmpl_links){
287 291 var s = YUD.get(k+'_link');
288 292 if(s){
289 293 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
290 294 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
291 295 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
292 296
293 297 var url = tmpl_links[k].replace('__CS__',new_cs.value);
294 298 var subrepos = YUD.get('archive_subrepos').checked;
295 299 url = url.replace('__SUB__',subrepos);
296 300 url = url.replace('__NAME__',title_tmpl);
297 301 s.innerHTML = url
298 302 }
299 303 }
300 304 });
301 305 </script>
302 306 %if c.show_stats:
303 307 <script type="text/javascript">
304 308 var data = ${c.trending_languages|n};
305 309 var total = 0;
306 310 var no_data = true;
307 311 var tbl = document.createElement('table');
308 312 tbl.setAttribute('class','trending_language_tbl');
309 313 var cnt = 0;
310 314 for (var i=0;i<data.length;i++){
311 315 total+= data[i][1].count;
312 316 }
313 317 for (var i=0;i<data.length;i++){
314 318 cnt += 1;
315 319 no_data = false;
316 320
317 321 var hide = cnt>2;
318 322 var tr = document.createElement('tr');
319 323 if (hide){
320 324 tr.setAttribute('style','display:none');
321 325 tr.setAttribute('class','stats_hidden');
322 326 }
323 327 var k = data[i][0];
324 328 var obj = data[i][1];
325 329 var percentage = Math.round((obj.count/total*100),2);
326 330
327 331 var td1 = document.createElement('td');
328 332 td1.width = 150;
329 333 var trending_language_label = document.createElement('div');
330 334 trending_language_label.innerHTML = obj.desc+" ("+k+")";
331 335 td1.appendChild(trending_language_label);
332 336
333 337 var td2 = document.createElement('td');
334 338 td2.setAttribute('style','padding-right:14px !important');
335 339 var trending_language = document.createElement('div');
336 340 var nr_files = obj.count+" ${_('files')}";
337 341
338 342 trending_language.title = k+" "+nr_files;
339 343
340 344 if (percentage>22){
341 345 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
342 346 }
343 347 else{
344 348 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
345 349 }
346 350
347 351 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
348 352 trending_language.style.width=percentage+"%";
349 353 td2.appendChild(trending_language);
350 354
351 355 tr.appendChild(td1);
352 356 tr.appendChild(td2);
353 357 tbl.appendChild(tr);
354 358 if(cnt == 3){
355 359 var show_more = document.createElement('tr');
356 360 var td = document.createElement('td');
357 361 lnk = document.createElement('a');
358 362
359 363 lnk.href='#';
360 364 lnk.innerHTML = "${_('show more')}";
361 365 lnk.id='code_stats_show_more';
362 366 td.appendChild(lnk);
363 367
364 368 show_more.appendChild(td);
365 369 show_more.appendChild(document.createElement('td'));
366 370 tbl.appendChild(show_more);
367 371 }
368 372
369 373 }
370 374
371 375 YUD.get('lang_stats').appendChild(tbl);
372 376 YUE.on('code_stats_show_more','click',function(){
373 377 l = YUD.getElementsByClassName('stats_hidden')
374 378 for (e in l){
375 379 YUD.setStyle(l[e],'display','');
376 380 };
377 381 YUD.setStyle(YUD.get('code_stats_show_more'),
378 382 'display','none');
379 383 });
380 384 </script>
381 385 <script type="text/javascript">
382 386 /**
383 387 * Plots summary graph
384 388 *
385 389 * @class SummaryPlot
386 390 * @param {from} initial from for detailed graph
387 391 * @param {to} initial to for detailed graph
388 392 * @param {dataset}
389 393 * @param {overview_dataset}
390 394 */
391 395 function SummaryPlot(from,to,dataset,overview_dataset) {
392 396 var initial_ranges = {
393 397 "xaxis":{
394 398 "from":from,
395 399 "to":to,
396 400 },
397 401 };
398 402 var dataset = dataset;
399 403 var overview_dataset = [overview_dataset];
400 404 var choiceContainer = YUD.get("legend_choices");
401 405 var choiceContainerTable = YUD.get("legend_choices_tables");
402 406 var plotContainer = YUD.get('commit_history');
403 407 var overviewContainer = YUD.get('overview');
404 408
405 409 var plot_options = {
406 410 bars: {show:true,align:'center',lineWidth:4},
407 411 legend: {show:true, container:"legend_container"},
408 412 points: {show:true,radius:0,fill:false},
409 413 yaxis: {tickDecimals:0,},
410 414 xaxis: {
411 415 mode: "time",
412 416 timeformat: "%d/%m",
413 417 min:from,
414 418 max:to,
415 419 },
416 420 grid: {
417 421 hoverable: true,
418 422 clickable: true,
419 423 autoHighlight:true,
420 424 color: "#999"
421 425 },
422 426 //selection: {mode: "x"}
423 427 };
424 428 var overview_options = {
425 429 legend:{show:false},
426 430 bars: {show:true,barWidth: 2,},
427 431 shadowSize: 0,
428 432 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
429 433 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
430 434 grid: {color: "#999",},
431 435 selection: {mode: "x"}
432 436 };
433 437
434 438 /**
435 439 *get dummy data needed in few places
436 440 */
437 441 function getDummyData(label){
438 442 return {"label":label,
439 443 "data":[{"time":0,
440 444 "commits":0,
441 445 "added":0,
442 446 "changed":0,
443 447 "removed":0,
444 448 }],
445 449 "schema":["commits"],
446 450 "color":'#ffffff',
447 451 }
448 452 }
449 453
450 454 /**
451 455 * generate checkboxes accordindly to data
452 456 * @param keys
453 457 * @returns
454 458 */
455 459 function generateCheckboxes(data) {
456 460 //append checkboxes
457 461 var i = 0;
458 462 choiceContainerTable.innerHTML = '';
459 463 for(var pos in data) {
460 464
461 465 data[pos].color = i;
462 466 i++;
463 467 if(data[pos].label != ''){
464 468 choiceContainerTable.innerHTML +=
465 469 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
466 470 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
467 471 }
468 472 }
469 473 }
470 474
471 475 /**
472 476 * ToolTip show
473 477 */
474 478 function showTooltip(x, y, contents) {
475 479 var div=document.getElementById('tooltip');
476 480 if(!div) {
477 481 div = document.createElement('div');
478 482 div.id="tooltip";
479 483 div.style.position="absolute";
480 484 div.style.border='1px solid #fdd';
481 485 div.style.padding='2px';
482 486 div.style.backgroundColor='#fee';
483 487 document.body.appendChild(div);
484 488 }
485 489 YUD.setStyle(div, 'opacity', 0);
486 490 div.innerHTML = contents;
487 491 div.style.top=(y + 5) + "px";
488 492 div.style.left=(x + 5) + "px";
489 493
490 494 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
491 495 anim.animate();
492 496 }
493 497
494 498 /**
495 499 * This function will detect if selected period has some changesets
496 500 for this user if it does this data is then pushed for displaying
497 501 Additionally it will only display users that are selected by the checkbox
498 502 */
499 503 function getDataAccordingToRanges(ranges) {
500 504
501 505 var data = [];
502 506 var new_dataset = {};
503 507 var keys = [];
504 508 var max_commits = 0;
505 509 for(var key in dataset){
506 510
507 511 for(var ds in dataset[key].data){
508 512 commit_data = dataset[key].data[ds];
509 513 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
510 514
511 515 if(new_dataset[key] === undefined){
512 516 new_dataset[key] = {data:[],schema:["commits"],label:key};
513 517 }
514 518 new_dataset[key].data.push(commit_data);
515 519 }
516 520 }
517 521 if (new_dataset[key] !== undefined){
518 522 data.push(new_dataset[key]);
519 523 }
520 524 }
521 525
522 526 if (data.length > 0){
523 527 return data;
524 528 }
525 529 else{
526 530 //just return dummy data for graph to plot itself
527 531 return [getDummyData('')];
528 532 }
529 533 }
530 534
531 535 /**
532 536 * redraw using new checkbox data
533 537 */
534 538 function plotchoiced(e,args){
535 539 var cur_data = args[0];
536 540 var cur_ranges = args[1];
537 541
538 542 var new_data = [];
539 543 var inputs = choiceContainer.getElementsByTagName("input");
540 544
541 545 //show only checked labels
542 546 for(var i=0; i<inputs.length; i++) {
543 547 var checkbox_key = inputs[i].name;
544 548
545 549 if(inputs[i].checked){
546 550 for(var d in cur_data){
547 551 if(cur_data[d].label == checkbox_key){
548 552 new_data.push(cur_data[d]);
549 553 }
550 554 }
551 555 }
552 556 else{
553 557 //push dummy data to not hide the label
554 558 new_data.push(getDummyData(checkbox_key));
555 559 }
556 560 }
557 561
558 562 var new_options = YAHOO.lang.merge(plot_options, {
559 563 xaxis: {
560 564 min: cur_ranges.xaxis.from,
561 565 max: cur_ranges.xaxis.to,
562 566 mode:"time",
563 567 timeformat: "%d/%m",
564 568 },
565 569 });
566 570 if (!new_data){
567 571 new_data = [[0,1]];
568 572 }
569 573 // do the zooming
570 574 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
571 575
572 576 plot.subscribe("plotselected", plotselected);
573 577
574 578 //resubscribe plothover
575 579 plot.subscribe("plothover", plothover);
576 580
577 581 // don't fire event on the overview to prevent eternal loop
578 582 overview.setSelection(cur_ranges, true);
579 583
580 584 }
581 585
582 586 /**
583 587 * plot only selected items from overview
584 588 * @param ranges
585 589 * @returns
586 590 */
587 591 function plotselected(ranges,cur_data) {
588 592 //updates the data for new plot
589 593 var data = getDataAccordingToRanges(ranges);
590 594 generateCheckboxes(data);
591 595
592 596 var new_options = YAHOO.lang.merge(plot_options, {
593 597 xaxis: {
594 598 min: ranges.xaxis.from,
595 599 max: ranges.xaxis.to,
596 600 mode:"time",
597 601 timeformat: "%d/%m",
598 602 },
599 603 });
600 604 // do the zooming
601 605 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
602 606
603 607 plot.subscribe("plotselected", plotselected);
604 608
605 609 //resubscribe plothover
606 610 plot.subscribe("plothover", plothover);
607 611
608 612 // don't fire event on the overview to prevent eternal loop
609 613 overview.setSelection(ranges, true);
610 614
611 615 //resubscribe choiced
612 616 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
613 617 }
614 618
615 619 var previousPoint = null;
616 620
617 621 function plothover(o) {
618 622 var pos = o.pos;
619 623 var item = o.item;
620 624
621 625 //YUD.get("x").innerHTML = pos.x.toFixed(2);
622 626 //YUD.get("y").innerHTML = pos.y.toFixed(2);
623 627 if (item) {
624 628 if (previousPoint != item.datapoint) {
625 629 previousPoint = item.datapoint;
626 630
627 631 var tooltip = YUD.get("tooltip");
628 632 if(tooltip) {
629 633 tooltip.parentNode.removeChild(tooltip);
630 634 }
631 635 var x = item.datapoint.x.toFixed(2);
632 636 var y = item.datapoint.y.toFixed(2);
633 637
634 638 if (!item.series.label){
635 639 item.series.label = 'commits';
636 640 }
637 641 var d = new Date(x*1000);
638 642 var fd = d.toDateString()
639 643 var nr_commits = parseInt(y);
640 644
641 645 var cur_data = dataset[item.series.label].data[item.dataIndex];
642 646 var added = cur_data.added;
643 647 var changed = cur_data.changed;
644 648 var removed = cur_data.removed;
645 649
646 650 var nr_commits_suffix = " ${_('commits')} ";
647 651 var added_suffix = " ${_('files added')} ";
648 652 var changed_suffix = " ${_('files changed')} ";
649 653 var removed_suffix = " ${_('files removed')} ";
650 654
651 655
652 656 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
653 657 if(added==1){added_suffix=" ${_('file added')} ";}
654 658 if(changed==1){changed_suffix=" ${_('file changed')} ";}
655 659 if(removed==1){removed_suffix=" ${_('file removed')} ";}
656 660
657 661 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
658 662 +'<br/>'+
659 663 nr_commits + nr_commits_suffix+'<br/>'+
660 664 added + added_suffix +'<br/>'+
661 665 changed + changed_suffix + '<br/>'+
662 666 removed + removed_suffix + '<br/>');
663 667 }
664 668 }
665 669 else {
666 670 var tooltip = YUD.get("tooltip");
667 671
668 672 if(tooltip) {
669 673 tooltip.parentNode.removeChild(tooltip);
670 674 }
671 675 previousPoint = null;
672 676 }
673 677 }
674 678
675 679 /**
676 680 * MAIN EXECUTION
677 681 */
678 682
679 683 var data = getDataAccordingToRanges(initial_ranges);
680 684 generateCheckboxes(data);
681 685
682 686 //main plot
683 687 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
684 688
685 689 //overview
686 690 var overview = YAHOO.widget.Flot(overviewContainer,
687 691 overview_dataset, overview_options);
688 692
689 693 //show initial selection on overview
690 694 overview.setSelection(initial_ranges);
691 695
692 696 plot.subscribe("plotselected", plotselected);
693 697 plot.subscribe("plothover", plothover)
694 698
695 699 overview.subscribe("plotselected", function (ranges) {
696 700 plot.setSelection(ranges);
697 701 });
698 702
699 703 // user choices on overview
700 704 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
701 705 }
702 706 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
703 707 </script>
704 708 %endif
705 709
706 710 </%def>
@@ -1,133 +1,149 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.tests.test_libs
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6
7 7 Package for testing various lib/helper functions in rhodecode
8 8
9 9 :created_on: Jun 9, 2011
10 10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or 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 unittest
27 27 import datetime
28 28 from rhodecode.tests import *
29 29
30 30
31 31 proto = 'http'
32 32 TEST_URLS = [
33 33 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
34 34 '%s://127.0.0.1' % proto),
35 35 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
36 36 '%s://127.0.0.1' % proto),
37 37 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
38 38 '%s://127.0.0.1' % proto),
39 39 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
40 40 '%s://127.0.0.1:8080' % proto),
41 41 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
42 42 '%s://domain.org' % proto),
43 43 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
44 44 '8080'],
45 45 '%s://domain.org:8080' % proto),
46 46 ]
47 47
48 48 proto = 'https'
49 49 TEST_URLS += [
50 50 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
51 51 '%s://127.0.0.1' % proto),
52 52 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
53 53 '%s://127.0.0.1' % proto),
54 54 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
55 55 '%s://127.0.0.1' % proto),
56 56 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
57 57 '%s://127.0.0.1:8080' % proto),
58 58 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
59 59 '%s://domain.org' % proto),
60 60 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
61 61 '8080'],
62 62 '%s://domain.org:8080' % proto),
63 63 ]
64 64
65 65
66 66 class TestLibs(unittest.TestCase):
67 67
68 68 def test_uri_filter(self):
69 69 from rhodecode.lib.utils2 import uri_filter
70 70
71 71 for url in TEST_URLS:
72 72 self.assertEqual(uri_filter(url[0]), url[1])
73 73
74 74 def test_credentials_filter(self):
75 75 from rhodecode.lib.utils2 import credentials_filter
76 76
77 77 for url in TEST_URLS:
78 78 self.assertEqual(credentials_filter(url[0]), url[2])
79 79
80 80 def test_str2bool(self):
81 81 from rhodecode.lib.utils2 import str2bool
82 82 test_cases = [
83 83 ('t', True),
84 84 ('true', True),
85 85 ('y', True),
86 86 ('yes', True),
87 87 ('on', True),
88 88 ('1', True),
89 89 ('Y', True),
90 90 ('yeS', True),
91 91 ('Y', True),
92 92 ('TRUE', True),
93 93 ('T', True),
94 94 ('False', False),
95 95 ('F', False),
96 96 ('FALSE', False),
97 97 ('0', False),
98 98 ('-1', False),
99 99 ('', False), ]
100 100
101 101 for case in test_cases:
102 102 self.assertEqual(str2bool(case[0]), case[1])
103 103
104 104 def test_mention_extractor(self):
105 105 from rhodecode.lib.utils2 import extract_mentioned_users
106 106 sample = (
107 107 "@first hi there @marcink here's my email marcin@email.com "
108 108 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
109 109 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
110 110 "@marian.user just do it @marco-polo and next extract @marco_polo "
111 111 "user.dot hej ! not-needed maril@domain.org"
112 112 )
113 113
114 114 s = sorted([
115 115 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
116 116 'marian.user', 'marco-polo', 'marco_polo'
117 117 ], key=lambda k: k.lower())
118 118 self.assertEqual(s, extract_mentioned_users(sample))
119 119
120 120 def test_age(self):
121 121 import calendar
122 122 from rhodecode.lib.utils2 import age
123 123 n = datetime.datetime.now()
124 124 delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
125 125 self.assertEqual(age(n), u'just now')
126 126 self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
127 127 self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
128 128 self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
129 129 self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
130 130 self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago')
131 131 self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month-1] + 2))),
132 132 u'1 month and 2 days ago')
133 133 self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago')
134
135 def test_tag_exctrator(self):
136 sample = (
137 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
138 "[requires] [stale] [see<>=>] [see => http://url.com]"
139 "[requires => url] [lang => python] [just a tag]"
140 "[,d] [ => ULR ] [obsolete] [desc]]"
141 )
142 from rhodecode.lib.helpers import desc_stylize
143 res = desc_stylize(sample)
144 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
145 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
146 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
147 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
148 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
149 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
General Comments 0
You need to be logged in to leave comments. Login now