##// END OF EJS Templates
#47 implemented Adding of new repo_groups+forms+validators. Fixed sorting of repo groups by main names in multiple locations. Removed some unneeded calls to self.sa for exchange to .query() methods....
marcink -
r1345:3bce31f0 beta
parent child Browse files
Show More
@@ -0,0 +1,133 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 users groups model for RhodeCode
7
8 :created_on: Jan 25, 2011
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 import os
27 import logging
28 import traceback
29
30 from pylons.i18n.translation import _
31
32 from vcs.utils.lazy import LazyProperty
33
34 from rhodecode.model import BaseModel
35 from rhodecode.model.caching_query import FromCache
36 from rhodecode.model.db import Group, RhodeCodeUi
37
38 log = logging.getLogger(__name__)
39
40
41 class ReposGroupModel(BaseModel):
42
43 @LazyProperty
44 def repos_path(self):
45 """
46 Get's the repositories root path from database
47 """
48
49 q = RhodeCodeUi.get_by_key('/').one()
50 return q.ui_value
51
52 def __create_group(self, group_name, parent_id):
53 """
54 makes repositories group on filesystem
55
56 :param repo_name:
57 :param parent_id:
58 """
59
60 if parent_id:
61 parent_group_name = Group.get(parent_id).group_name
62 else:
63 parent_group_name = ''
64
65 create_path = os.path.join(self.repos_path, parent_group_name,
66 group_name)
67 log.debug('creating new group in %s', create_path)
68
69 if os.path.isdir(create_path):
70 raise Exception('That directory already exists !')
71
72
73 os.makedirs(create_path)
74
75
76 def __rename_group(self, group_name):
77 """
78 Renames a group on filesystem
79
80 :param group_name:
81 """
82 pass
83
84 def __delete_group(self, group_name):
85 """
86 Deletes a group from a filesystem
87
88 :param group_name:
89 """
90 pass
91
92 def create(self, form_data):
93 try:
94 new_repos_group = Group()
95 new_repos_group.group_name = form_data['repos_group_name']
96 new_repos_group.group_description = \
97 form_data['repos_group_description']
98 new_repos_group.group_parent_id = form_data['repos_group_parent']
99
100 self.sa.add(new_repos_group)
101
102 self.__create_group(form_data['repos_group_name'],
103 form_data['repos_group_parent'])
104
105 self.sa.commit()
106 except:
107 log.error(traceback.format_exc())
108 self.sa.rollback()
109 raise
110
111 def update(self, repos_group_id, form_data):
112
113 try:
114 repos_group = Group.get(repos_group_id)
115
116
117
118 self.sa.add(repos_group)
119 self.sa.commit()
120 except:
121 log.error(traceback.format_exc())
122 self.sa.rollback()
123 raise
124
125 def delete(self, users_group_id):
126 try:
127 users_group = self.get(users_group_id, cache=False)
128 self.sa.delete(users_group)
129 self.sa.commit()
130 except:
131 log.error(traceback.format_exc())
132 self.sa.rollback()
133 raise
@@ -0,0 +1,64 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('Add repos group')} - ${c.rhodecode_name}
6 </%def>
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
12 ${_('add new repos group')}
13 </%def>
14
15 <%def name="page_nav()">
16 ${self.menu('admin')}
17 </%def>
18
19 <%def name="main()">
20 <div class="box">
21 <!-- box / title -->
22 <div class="title">
23 ${self.breadcrumbs()}
24 </div>
25 <!-- end box / title -->
26 ${h.form(url('repos_groups'))}
27 <div class="form">
28 <!-- fields -->
29 <div class="fields">
30 <div class="field">
31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
33 </div>
34 <div class="input">
35 ${h.text('repos_group_name',class_='medium')}
36 </div>
37 </div>
38
39 <div class="field">
40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
42 </div>
43 <div class="textarea text-area editor">
44 ${h.textarea('repos_group_description',cols=23,rows=5,class_="medium")}
45 </div>
46 </div>
47
48 <div class="field">
49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
51 </div>
52 <div class="input">
53 ${h.select('repos_group_parent','',c.repo_groups,class_="medium")}
54 </div>
55 </div>
56
57 <div class="buttons">
58 ${h.submit('save','save',class_="ui-button")}
59 </div>
60 </div>
61 </div>
62 ${h.end_form()}
63 </div>
64 </%def> No newline at end of file
@@ -0,0 +1,68 b''
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
3
4 <%def name="title()">
5 ${_('Repositories groups administration')} - ${c.rhodecode_name}
6 </%def>
7
8
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 </%def>
12 <%def name="page_nav()">
13 ${self.menu('admin')}
14 </%def>
15 <%def name="main()">
16 <div class="box">
17 <!-- box / title -->
18 <div class="title">
19 ${self.breadcrumbs()}
20 <ul class="links">
21 <li>
22 <span>${h.link_to(u'ADD NEW GROUP',h.url('new_repos_group'))}</span>
23 </li>
24 </ul>
25 </div>
26 <!-- end box / title -->
27 <div class="table">
28 % if c.groups:
29 <table class="table_disp">
30
31 <thead>
32 <tr>
33 <th class="left"><a href="#">${_('Group name')}</a></th>
34 <th class="left"><a href="#">${_('Description')}</a></th>
35 <th class="left"><a href="#">${_('Number of repositories')}</a></th>
36 <th class="left">${_('action')}</th>
37 </tr>
38 </thead>
39
40 ## REPO GROUPS
41
42 % for gr in c.groups:
43 <tr>
44 <td>
45 <div style="white-space: nowrap">
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
47 ${h.link_to(h.literal(' &raquo; '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
48 </div>
49 </td>
50 <td>${gr.group_description}</td>
51 <td><b>${gr.repositories.count()}</b></td>
52 <td>
53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
54 ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this group');")}
55 ${h.end_form()}
56 </td>
57 </tr>
58 % endfor
59
60 </table>
61 % else:
62 {_('There are no repositories groups yet')}
63 % endif
64
65 </div>
66 </div>
67
68 </%def>
@@ -1,418 +1,420 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Admin controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from operator import itemgetter
30 30 from formencode import htmlfill
31 31
32 32 from paste.httpexceptions import HTTPInternalServerError
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 44 from rhodecode.model.forms import RepoForm
45 45 from rhodecode.model.scm import ScmModel
46 46 from rhodecode.model.repo import RepoModel
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class ReposController(BaseController):
52 52 """
53 53 REST Controller styled on the Atom Publishing Protocol"""
54 54 # To properly map this controller, ensure your config/routing.py
55 55 # file has a resource setup:
56 56 # map.resource('repo', 'repos')
57 57
58 58 @LoginRequired()
59 59 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 60 def __before__(self):
61 61 c.admin_user = session.get('admin_user')
62 62 c.admin_username = session.get('admin_username')
63 63 super(ReposController, self).__before__()
64 64
65 65 def __load_defaults(self):
66 66 repo_model = RepoModel()
67 67
68 68 c.repo_groups = [('', '')]
69 69 parents_link = lambda k: h.literal('&raquo;'.join(
70 70 map(lambda k: k.group_name,
71 71 k.parents + [k])
72 72 )
73 73 )
74 74
75 75 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
76 76 x in self.sa.query(Group).all()])
77 c.repo_groups = sorted(c.repo_groups,
78 key=lambda t: t[1].split('&raquo;')[0])
77 79 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
78 80 c.users_array = repo_model.get_users_js()
79 81 c.users_groups_array = repo_model.get_users_groups_js()
80 82
81 83 def __load_data(self, repo_name=None):
82 84 """
83 85 Load defaults settings for edit, and update
84 86
85 87 :param repo_name:
86 88 """
87 89 self.__load_defaults()
88 90
89 91 repo, dbrepo = ScmModel().get(repo_name, retval='repo')
90 92
91 93 repo_model = RepoModel()
92 94 c.repo_info = repo_model.get_by_repo_name(repo_name)
93 95
94 96 if c.repo_info is None:
95 97 h.flash(_('%s repository is not mapped to db perhaps'
96 98 ' it was created or renamed from the filesystem'
97 99 ' please run the application again'
98 100 ' in order to rescan repositories') % repo_name,
99 101 category='error')
100 102
101 103 return redirect(url('repos'))
102 104
103 105 c.default_user_id = User.by_username('default').user_id
104 106 c.in_public_journal = self.sa.query(UserFollowing)\
105 107 .filter(UserFollowing.user_id == c.default_user_id)\
106 108 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
107 109
108 110 if c.repo_info.stats:
109 111 last_rev = c.repo_info.stats.stat_on_revision
110 112 else:
111 113 last_rev = 0
112 114 c.stats_revision = last_rev
113 115
114 116 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
115 117
116 118 if last_rev == 0 or c.repo_last_rev == 0:
117 119 c.stats_percentage = 0
118 120 else:
119 121 c.stats_percentage = '%.2f' % ((float((last_rev)) /
120 122 c.repo_last_rev) * 100)
121 123
122 124 defaults = c.repo_info.get_dict()
123 125 group, repo_name = c.repo_info.groups_and_repo
124 126 defaults['repo_name'] = repo_name
125 127 defaults['repo_group'] = getattr(group[-1] if group else None,
126 128 'group_id', None)
127 129
128 130 #fill owner
129 131 if c.repo_info.user:
130 132 defaults.update({'user': c.repo_info.user.username})
131 133 else:
132 134 replacement_user = self.sa.query(User)\
133 135 .filter(User.admin == True).first().username
134 136 defaults.update({'user': replacement_user})
135 137
136 138 #fill repository users
137 139 for p in c.repo_info.repo_to_perm:
138 140 defaults.update({'u_perm_%s' % p.user.username:
139 141 p.permission.permission_name})
140 142
141 143 #fill repository groups
142 144 for p in c.repo_info.users_group_to_perm:
143 145 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
144 146 p.permission.permission_name})
145 147
146 148 return defaults
147 149
148 150 @HasPermissionAllDecorator('hg.admin')
149 151 def index(self, format='html'):
150 152 """GET /repos: All items in the collection"""
151 153 # url('repos')
152 154
153 155 all_repos = [r.repo_name for r in Repository.query().all()]
154 156
155 157 cached_repo_list = ScmModel().get_repos(all_repos)
156 158 c.repos_list = sorted(cached_repo_list, key=itemgetter('name_sort'))
157 159 return render('admin/repos/repos.html')
158 160
159 161 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
160 162 def create(self):
161 163 """
162 164 POST /repos: Create a new item"""
163 165 # url('repos')
164 166 repo_model = RepoModel()
165 167 self.__load_defaults()
166 168 form_result = {}
167 169 try:
168 170 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
169 171 .to_python(dict(request.POST))
170 172 repo_model.create(form_result, self.rhodecode_user)
171 173 if form_result['clone_uri']:
172 174 h.flash(_('created repository %s from %s') \
173 175 % (form_result['repo_name'], form_result['clone_uri']),
174 176 category='success')
175 177 else:
176 178 h.flash(_('created repository %s') % form_result['repo_name'],
177 179 category='success')
178 180
179 181 if request.POST.get('user_created'):
180 182 action_logger(self.rhodecode_user, 'user_created_repo',
181 183 form_result['repo_name'], '', self.sa)
182 184 else:
183 185 action_logger(self.rhodecode_user, 'admin_created_repo',
184 186 form_result['repo_name'], '', self.sa)
185 187
186 188 except formencode.Invalid, errors:
187 189
188 190 c.new_repo = errors.value['repo_name']
189 191
190 192 if request.POST.get('user_created'):
191 193 r = render('admin/repos/repo_add_create_repository.html')
192 194 else:
193 195 r = render('admin/repos/repo_add.html')
194 196
195 197 return htmlfill.render(
196 198 r,
197 199 defaults=errors.value,
198 200 errors=errors.error_dict or {},
199 201 prefix_error=False,
200 202 encoding="UTF-8")
201 203
202 204 except Exception:
203 205 log.error(traceback.format_exc())
204 206 msg = _('error occurred during creation of repository %s') \
205 207 % form_result.get('repo_name')
206 208 h.flash(msg, category='error')
207 209 if request.POST.get('user_created'):
208 210 return redirect(url('home'))
209 211 return redirect(url('repos'))
210 212
211 213 @HasPermissionAllDecorator('hg.admin')
212 214 def new(self, format='html'):
213 215 """GET /repos/new: Form to create a new item"""
214 216 new_repo = request.GET.get('repo', '')
215 217 c.new_repo = repo_name_slug(new_repo)
216 218 self.__load_defaults()
217 219 return render('admin/repos/repo_add.html')
218 220
219 221 @HasPermissionAllDecorator('hg.admin')
220 222 def update(self, repo_name):
221 223 """
222 224 PUT /repos/repo_name: Update an existing item"""
223 225 # Forms posted to this method should contain a hidden field:
224 226 # <input type="hidden" name="_method" value="PUT" />
225 227 # Or using helpers:
226 228 # h.form(url('repo', repo_name=ID),
227 229 # method='put')
228 230 # url('repo', repo_name=ID)
229 231 self.__load_defaults()
230 232 repo_model = RepoModel()
231 233 changed_name = repo_name
232 234 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
233 235 repo_groups=c.repo_groups_choices)()
234 236 try:
235 237 form_result = _form.to_python(dict(request.POST))
236 238 repo_model.update(repo_name, form_result)
237 239 invalidate_cache('get_repo_cached_%s' % repo_name)
238 240 h.flash(_('Repository %s updated successfully' % repo_name),
239 241 category='success')
240 242 changed_name = form_result['repo_name_full']
241 243 action_logger(self.rhodecode_user, 'admin_updated_repo',
242 244 changed_name, '', self.sa)
243 245
244 246 except formencode.Invalid, errors:
245 247 defaults = self.__load_data(repo_name)
246 248 defaults.update(errors.value)
247 249 return htmlfill.render(
248 250 render('admin/repos/repo_edit.html'),
249 251 defaults=defaults,
250 252 errors=errors.error_dict or {},
251 253 prefix_error=False,
252 254 encoding="UTF-8")
253 255
254 256 except Exception:
255 257 log.error(traceback.format_exc())
256 258 h.flash(_('error occurred during update of repository %s') \
257 259 % repo_name, category='error')
258 260 return redirect(url('edit_repo', repo_name=changed_name))
259 261
260 262 @HasPermissionAllDecorator('hg.admin')
261 263 def delete(self, repo_name):
262 264 """
263 265 DELETE /repos/repo_name: Delete an existing item"""
264 266 # Forms posted to this method should contain a hidden field:
265 267 # <input type="hidden" name="_method" value="DELETE" />
266 268 # Or using helpers:
267 269 # h.form(url('repo', repo_name=ID),
268 270 # method='delete')
269 271 # url('repo', repo_name=ID)
270 272
271 273 repo_model = RepoModel()
272 274 repo = repo_model.get_by_repo_name(repo_name)
273 275 if not repo:
274 276 h.flash(_('%s repository is not mapped to db perhaps'
275 277 ' it was moved or renamed from the filesystem'
276 278 ' please run the application again'
277 279 ' in order to rescan repositories') % repo_name,
278 280 category='error')
279 281
280 282 return redirect(url('repos'))
281 283 try:
282 284 action_logger(self.rhodecode_user, 'admin_deleted_repo',
283 285 repo_name, '', self.sa)
284 286 repo_model.delete(repo)
285 287 invalidate_cache('get_repo_cached_%s' % repo_name)
286 288 h.flash(_('deleted repository %s') % repo_name, category='success')
287 289
288 290 except Exception, e:
289 291 log.error(traceback.format_exc())
290 292 h.flash(_('An error occurred during deletion of %s') % repo_name,
291 293 category='error')
292 294
293 295 return redirect(url('repos'))
294 296
295 297 @HasPermissionAllDecorator('hg.admin')
296 298 def delete_perm_user(self, repo_name):
297 299 """
298 300 DELETE an existing repository permission user
299 301
300 302 :param repo_name:
301 303 """
302 304
303 305 try:
304 306 repo_model = RepoModel()
305 307 repo_model.delete_perm_user(request.POST, repo_name)
306 308 except Exception, e:
307 309 h.flash(_('An error occurred during deletion of repository user'),
308 310 category='error')
309 311 raise HTTPInternalServerError()
310 312
311 313 @HasPermissionAllDecorator('hg.admin')
312 314 def delete_perm_users_group(self, repo_name):
313 315 """
314 316 DELETE an existing repository permission users group
315 317
316 318 :param repo_name:
317 319 """
318 320 try:
319 321 repo_model = RepoModel()
320 322 repo_model.delete_perm_users_group(request.POST, repo_name)
321 323 except Exception, e:
322 324 h.flash(_('An error occurred during deletion of repository'
323 325 ' users groups'),
324 326 category='error')
325 327 raise HTTPInternalServerError()
326 328
327 329 @HasPermissionAllDecorator('hg.admin')
328 330 def repo_stats(self, repo_name):
329 331 """
330 332 DELETE an existing repository statistics
331 333
332 334 :param repo_name:
333 335 """
334 336
335 337 try:
336 338 repo_model = RepoModel()
337 339 repo_model.delete_stats(repo_name)
338 340 except Exception, e:
339 341 h.flash(_('An error occurred during deletion of repository stats'),
340 342 category='error')
341 343 return redirect(url('edit_repo', repo_name=repo_name))
342 344
343 345 @HasPermissionAllDecorator('hg.admin')
344 346 def repo_cache(self, repo_name):
345 347 """
346 348 INVALIDATE existing repository cache
347 349
348 350 :param repo_name:
349 351 """
350 352
351 353 try:
352 354 ScmModel().mark_for_invalidation(repo_name)
353 355 except Exception, e:
354 356 h.flash(_('An error occurred during cache invalidation'),
355 357 category='error')
356 358 return redirect(url('edit_repo', repo_name=repo_name))
357 359
358 360 @HasPermissionAllDecorator('hg.admin')
359 361 def repo_public_journal(self, repo_name):
360 362 """
361 363 Set's this repository to be visible in public journal,
362 364 in other words assing default user to follow this repo
363 365
364 366 :param repo_name:
365 367 """
366 368
367 369 cur_token = request.POST.get('auth_token')
368 370 token = get_token()
369 371 if cur_token == token:
370 372 try:
371 373 repo_id = Repository.by_repo_name(repo_name).repo_id
372 374 user_id = User.by_username('default').user_id
373 375 self.scm_model.toggle_following_repo(repo_id, user_id)
374 376 h.flash(_('Updated repository visibility in public journal'),
375 377 category='success')
376 378 except:
377 379 h.flash(_('An error occurred during setting this'
378 380 ' repository in public journal'),
379 381 category='error')
380 382
381 383 else:
382 384 h.flash(_('Token mismatch'), category='error')
383 385 return redirect(url('edit_repo', repo_name=repo_name))
384 386
385 387 @HasPermissionAllDecorator('hg.admin')
386 388 def repo_pull(self, repo_name):
387 389 """
388 390 Runs task to update given repository with remote changes,
389 391 ie. make pull on remote location
390 392
391 393 :param repo_name:
392 394 """
393 395 try:
394 396 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
395 397 h.flash(_('Pulled from remote location'), category='success')
396 398 except Exception, e:
397 399 h.flash(_('An error occurred during pull from remote location'),
398 400 category='error')
399 401
400 402 return redirect(url('edit_repo', repo_name=repo_name))
401 403
402 404 @HasPermissionAllDecorator('hg.admin')
403 405 def show(self, repo_name, format='html'):
404 406 """GET /repos/repo_name: Show a specific item"""
405 407 # url('repo', repo_name=ID)
406 408
407 409 @HasPermissionAllDecorator('hg.admin')
408 410 def edit(self, repo_name, format='html'):
409 411 """GET /repos/repo_name/edit: Form to edit an existing item"""
410 412 # url('edit_repo', repo_name=ID)
411 413 defaults = self.__load_data(repo_name)
412 414
413 415 return htmlfill.render(
414 416 render('admin/repos/repo_edit.html'),
415 417 defaults=defaults,
416 418 encoding="UTF-8",
417 419 force_defaults=False
418 420 )
@@ -1,93 +1,160 b''
1 1 import logging
2 import traceback
3 import formencode
4
5 from formencode import htmlfill
2 6 from operator import itemgetter
3 7
4 8 from pylons import request, response, session, tmpl_context as c, url
5 9 from pylons.controllers.util import abort, redirect
10 from pylons.i18n.translation import _
6 11
12 from rhodecode.lib import helpers as h
13 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
14 HasPermissionAnyDecorator
7 15 from rhodecode.lib.base import BaseController, render
8 16 from rhodecode.model.db import Group
17 from rhodecode.model.repos_group import ReposGroupModel
18 from rhodecode.model.forms import ReposGroupForm
9 19
10 20 log = logging.getLogger(__name__)
11 21
12 22
13 23 class ReposGroupsController(BaseController):
14 24 """REST Controller styled on the Atom Publishing Protocol"""
15 25 # To properly map this controller, ensure your config/routing.py
16 26 # file has a resource setup:
17 27 # map.resource('repos_group', 'repos_groups')
18 28
29 def __load_defaults(self):
30
31 c.repo_groups = [('', '')]
32 parents_link = lambda k: h.literal('&raquo;'.join(
33 map(lambda k: k.group_name,
34 k.parents + [k])
35 )
36 )
37
38 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
39 x in self.sa.query(Group).all()])
40
41 c.repo_groups = sorted(c.repo_groups,
42 key=lambda t: t[1].split('&raquo;')[0])
43 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
44
45 @LoginRequired()
46 def __before__(self):
47 super(ReposGroupsController, self).__before__()
48
49 @HasPermissionAnyDecorator('hg.admin')
19 50 def index(self, format='html'):
20 51 """GET /repos_groups: All items in the collection"""
21 52 # url('repos_groups')
22 53
54 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
55 c.groups = sorted(Group.query().all(), key=sk)
56 return render('admin/repos_groups/repos_groups_show.html')
57
58 @HasPermissionAnyDecorator('hg.admin')
23 59 def create(self):
24 60 """POST /repos_groups: Create a new item"""
25 61 # url('repos_groups')
62 self.__load_defaults()
63 repos_group_model = ReposGroupModel()
64 repos_group_form = ReposGroupForm(available_groups=
65 c.repo_groups_choices)()
66 try:
67 form_result = repos_group_form.to_python(dict(request.POST))
68 repos_group_model.create(form_result)
69 h.flash(_('created repos group %s') \
70 % form_result['repos_group_name'], category='success')
71 #TODO: in futureaction_logger(, '', '', '', self.sa)
72 except formencode.Invalid, errors:
26 73
74 return htmlfill.render(
75 render('admin/repos_groups/repos_groups_add.html'),
76 defaults=errors.value,
77 errors=errors.error_dict or {},
78 prefix_error=False,
79 encoding="UTF-8")
80 except Exception:
81 log.error(traceback.format_exc())
82 h.flash(_('error occurred during creation of repos group %s') \
83 % request.POST.get('repos_group_name'), category='error')
84
85 return redirect(url('repos_groups'))
86
87
88 @HasPermissionAnyDecorator('hg.admin')
27 89 def new(self, format='html'):
28 90 """GET /repos_groups/new: Form to create a new item"""
29 91 # url('new_repos_group')
92 self.__load_defaults()
93 return render('admin/repos_groups/repos_groups_add.html')
30 94
95 @HasPermissionAnyDecorator('hg.admin')
31 96 def update(self, id):
32 97 """PUT /repos_groups/id: Update an existing item"""
33 98 # Forms posted to this method should contain a hidden field:
34 99 # <input type="hidden" name="_method" value="PUT" />
35 100 # Or using helpers:
36 101 # h.form(url('repos_group', id=ID),
37 102 # method='put')
38 103 # url('repos_group', id=ID)
39 104
105 @HasPermissionAnyDecorator('hg.admin')
40 106 def delete(self, id):
41 107 """DELETE /repos_groups/id: Delete an existing item"""
42 108 # Forms posted to this method should contain a hidden field:
43 109 # <input type="hidden" name="_method" value="DELETE" />
44 110 # Or using helpers:
45 111 # h.form(url('repos_group', id=ID),
46 112 # method='delete')
47 113 # url('repos_group', id=ID)
48 114
49 115 def show(self, id, format='html'):
50 116 """GET /repos_groups/id: Show a specific item"""
51 117 # url('repos_group', id=ID)
52 118
53 119 c.group = Group.get(id)
54 120 if c.group:
55 121 c.group_repos = c.group.repositories.all()
56 122 else:
57 123 return redirect(url('repos_group'))
58 124
59 125 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
60 126 current_sort = request.GET.get('sort', 'name')
61 127 current_sort_slug = current_sort.replace('-', '')
62 128
63 129 if current_sort_slug not in sortables:
64 130 c.sort_by = 'name'
65 131 current_sort_slug = c.sort_by
66 132 else:
67 133 c.sort_by = current_sort
68 134 c.sort_slug = current_sort_slug
69 135
70 136 sort_key = current_sort_slug + '_sort'
71 137
72 138 #overwrite our cached list with current filter
73 139 gr_filter = [r.repo_name for r in c.group_repos]
74 140 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
75 141
76 142 if c.sort_by.startswith('-'):
77 143 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
78 144 reverse=True)
79 145 else:
80 146 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
81 147 reverse=False)
82 148
83 149 c.repo_cnt = len(c.repos_list)
84 150
85 151
86 152 c.groups = self.sa.query(Group).order_by(Group.group_name)\
87 153 .filter(Group.group_parent_id == id).all()
88 154
89 155 return render('admin/repos_groups/repos_groups.html')
90 156
157 @HasPermissionAnyDecorator('hg.admin')
91 158 def edit(self, id, format='html'):
92 159 """GET /repos_groups/id/edit: Form to edit an existing item"""
93 160 # url('edit_repos_group', id=ID)
@@ -1,362 +1,364 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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from sqlalchemy import func
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 abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from rhodecode.lib import helpers as h
37 37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 38 HasPermissionAnyDecorator, NotAnonymous
39 39 from rhodecode.lib.base import BaseController, render
40 40 from rhodecode.lib.celerylib import tasks, run_task
41 41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 42 set_rhodecode_config, repo_name_slug
43 43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 44 RhodeCodeSettings
45 45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 46 ApplicationUiSettingsForm
47 47 from rhodecode.model.scm import ScmModel
48 48 from rhodecode.model.user import UserModel
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 class SettingsController(BaseController):
54 54 """REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('setting', 'settings', controller='admin/settings',
58 58 # path_prefix='/admin', name_prefix='admin_')
59 59
60 60 @LoginRequired()
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(SettingsController, self).__before__()
65 65
66 66 @HasPermissionAllDecorator('hg.admin')
67 67 def index(self, format='html'):
68 68 """GET /admin/settings: All items in the collection"""
69 69 # url('admin_settings')
70 70
71 71 defaults = RhodeCodeSettings.get_app_settings()
72 72 defaults.update(self.get_hg_ui_settings())
73 73 return htmlfill.render(
74 74 render('admin/settings/settings.html'),
75 75 defaults=defaults,
76 76 encoding="UTF-8",
77 77 force_defaults=False
78 78 )
79 79
80 80 @HasPermissionAllDecorator('hg.admin')
81 81 def create(self):
82 82 """POST /admin/settings: Create a new item"""
83 83 # url('admin_settings')
84 84
85 85 @HasPermissionAllDecorator('hg.admin')
86 86 def new(self, format='html'):
87 87 """GET /admin/settings/new: Form to create a new item"""
88 88 # url('admin_new_setting')
89 89
90 90 @HasPermissionAllDecorator('hg.admin')
91 91 def update(self, setting_id):
92 92 """PUT /admin/settings/setting_id: Update an existing item"""
93 93 # Forms posted to this method should contain a hidden field:
94 94 # <input type="hidden" name="_method" value="PUT" />
95 95 # Or using helpers:
96 96 # h.form(url('admin_setting', setting_id=ID),
97 97 # method='put')
98 98 # url('admin_setting', setting_id=ID)
99 99 if setting_id == 'mapping':
100 100 rm_obsolete = request.POST.get('destroy', False)
101 101 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102 102 initial = ScmModel().repo_scan()
103 103 log.debug('invalidating all repositories')
104 104 for repo_name in initial.keys():
105 105 invalidate_cache('get_repo_cached_%s' % repo_name)
106 106
107 107 added, removed = repo2db_mapper(initial, rm_obsolete)
108 108
109 109 h.flash(_('Repositories successfully'
110 110 ' rescanned added: %s,removed: %s') % (added, removed),
111 111 category='success')
112 112
113 113 if setting_id == 'whoosh':
114 114 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 115 full_index = request.POST.get('full_index', False)
116 116 run_task(tasks.whoosh_index, repo_location, full_index)
117 117
118 118 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 119 if setting_id == 'global':
120 120
121 121 application_form = ApplicationSettingsForm()()
122 122 try:
123 123 form_result = application_form.to_python(dict(request.POST))
124 124
125 125 try:
126 126 hgsettings1 = RhodeCodeSettings.get_by_name('title')
127 127 hgsettings1.app_settings_value = \
128 128 form_result['rhodecode_title']
129 129
130 130 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
131 131 hgsettings2.app_settings_value = \
132 132 form_result['rhodecode_realm']
133 133
134 134 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
135 135 hgsettings3.app_settings_value = \
136 136 form_result['rhodecode_ga_code']
137 137
138 138 self.sa.add(hgsettings1)
139 139 self.sa.add(hgsettings2)
140 140 self.sa.add(hgsettings3)
141 141 self.sa.commit()
142 142 set_rhodecode_config(config)
143 143 h.flash(_('Updated application settings'),
144 144 category='success')
145 145
146 146 except Exception:
147 147 log.error(traceback.format_exc())
148 148 h.flash(_('error occurred during updating '
149 149 'application settings'),
150 150 category='error')
151 151
152 152 self.sa.rollback()
153 153
154 154 except formencode.Invalid, errors:
155 155 return htmlfill.render(
156 156 render('admin/settings/settings.html'),
157 157 defaults=errors.value,
158 158 errors=errors.error_dict or {},
159 159 prefix_error=False,
160 160 encoding="UTF-8")
161 161
162 162 if setting_id == 'mercurial':
163 163 application_form = ApplicationUiSettingsForm()()
164 164 try:
165 165 form_result = application_form.to_python(dict(request.POST))
166 166
167 167 try:
168 168
169 169 hgsettings1 = self.sa.query(RhodeCodeUi)\
170 170 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
171 171 hgsettings1.ui_value = form_result['web_push_ssl']
172 172
173 173 hgsettings2 = self.sa.query(RhodeCodeUi)\
174 174 .filter(RhodeCodeUi.ui_key == '/').one()
175 175 hgsettings2.ui_value = form_result['paths_root_path']
176 176
177 177 #HOOKS
178 178 hgsettings3 = self.sa.query(RhodeCodeUi)\
179 179 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
180 180 hgsettings3.ui_active = \
181 181 bool(form_result['hooks_changegroup_update'])
182 182
183 183 hgsettings4 = self.sa.query(RhodeCodeUi)\
184 184 .filter(RhodeCodeUi.ui_key ==
185 185 'changegroup.repo_size').one()
186 186 hgsettings4.ui_active = \
187 187 bool(form_result['hooks_changegroup_repo_size'])
188 188
189 189 hgsettings5 = self.sa.query(RhodeCodeUi)\
190 190 .filter(RhodeCodeUi.ui_key ==
191 191 'pretxnchangegroup.push_logger').one()
192 192 hgsettings5.ui_active = \
193 193 bool(form_result['hooks_pretxnchangegroup'
194 194 '_push_logger'])
195 195
196 196 hgsettings6 = self.sa.query(RhodeCodeUi)\
197 197 .filter(RhodeCodeUi.ui_key ==
198 198 'preoutgoing.pull_logger').one()
199 199 hgsettings6.ui_active = \
200 200 bool(form_result['hooks_preoutgoing_pull_logger'])
201 201
202 202 self.sa.add(hgsettings1)
203 203 self.sa.add(hgsettings2)
204 204 self.sa.add(hgsettings3)
205 205 self.sa.add(hgsettings4)
206 206 self.sa.add(hgsettings5)
207 207 self.sa.add(hgsettings6)
208 208 self.sa.commit()
209 209
210 210 h.flash(_('Updated mercurial settings'),
211 211 category='success')
212 212
213 213 except:
214 214 log.error(traceback.format_exc())
215 215 h.flash(_('error occurred during updating '
216 216 'application settings'), category='error')
217 217
218 218 self.sa.rollback()
219 219
220 220 except formencode.Invalid, errors:
221 221 return htmlfill.render(
222 222 render('admin/settings/settings.html'),
223 223 defaults=errors.value,
224 224 errors=errors.error_dict or {},
225 225 prefix_error=False,
226 226 encoding="UTF-8")
227 227
228 228 return redirect(url('admin_settings'))
229 229
230 230 @HasPermissionAllDecorator('hg.admin')
231 231 def delete(self, setting_id):
232 232 """DELETE /admin/settings/setting_id: Delete an existing item"""
233 233 # Forms posted to this method should contain a hidden field:
234 234 # <input type="hidden" name="_method" value="DELETE" />
235 235 # Or using helpers:
236 236 # h.form(url('admin_setting', setting_id=ID),
237 237 # method='delete')
238 238 # url('admin_setting', setting_id=ID)
239 239
240 240 @HasPermissionAllDecorator('hg.admin')
241 241 def show(self, setting_id, format='html'):
242 242 """
243 243 GET /admin/settings/setting_id: Show a specific item"""
244 244 # url('admin_setting', setting_id=ID)
245 245
246 246 @HasPermissionAllDecorator('hg.admin')
247 247 def edit(self, setting_id, format='html'):
248 248 """
249 249 GET /admin/settings/setting_id/edit: Form to
250 250 edit an existing item"""
251 251 # url('admin_edit_setting', setting_id=ID)
252 252
253 253 @NotAnonymous()
254 254 def my_account(self):
255 255 """
256 256 GET /_admin/my_account Displays info about my account
257 257 """
258 258 # url('admin_settings_my_account')
259 259
260 260 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
261 261 all_repos = [r.repo_name for r in self.sa.query(Repository)\
262 262 .filter(Repository.user_id == c.user.user_id)\
263 263 .order_by(func.lower(Repository.repo_name)).all()]
264 264 c.user_repos = ScmModel().get_repos(all_repos)
265 265
266 266 if c.user.username == 'default':
267 267 h.flash(_("You can't edit this user since it's"
268 268 " crucial for entire application"), category='warning')
269 269 return redirect(url('users'))
270 270
271 271 defaults = c.user.get_dict()
272 272 return htmlfill.render(
273 273 render('admin/users/user_edit_my_account.html'),
274 274 defaults=defaults,
275 275 encoding="UTF-8",
276 276 force_defaults=False
277 277 )
278 278
279 279 def my_account_update(self):
280 280 """PUT /_admin/my_account_update: Update an existing item"""
281 281 # Forms posted to this method should contain a hidden field:
282 282 # <input type="hidden" name="_method" value="PUT" />
283 283 # Or using helpers:
284 284 # h.form(url('admin_settings_my_account_update'),
285 285 # method='put')
286 286 # url('admin_settings_my_account_update', id=ID)
287 287 user_model = UserModel()
288 288 uid = self.rhodecode_user.user_id
289 289 _form = UserForm(edit=True,
290 290 old_data={'user_id': uid,
291 291 'email': self.rhodecode_user.email})()
292 292 form_result = {}
293 293 try:
294 294 form_result = _form.to_python(dict(request.POST))
295 295 user_model.update_my_account(uid, form_result)
296 296 h.flash(_('Your account was updated successfully'),
297 297 category='success')
298 298
299 299 except formencode.Invalid, errors:
300 300 c.user = user_model.get(self.rhodecode_user.user_id, cache=False)
301 301 c.user = UserModel().get(self.rhodecode_user.user_id, cache=False)
302 302 all_repos = self.sa.query(Repository)\
303 303 .filter(Repository.user_id == c.user.user_id)\
304 304 .order_by(func.lower(Repository.repo_name))\
305 305 .all()
306 306 c.user_repos = ScmModel().get_repos(all_repos)
307 307
308 308 return htmlfill.render(
309 309 render('admin/users/user_edit_my_account.html'),
310 310 defaults=errors.value,
311 311 errors=errors.error_dict or {},
312 312 prefix_error=False,
313 313 encoding="UTF-8")
314 314 except Exception:
315 315 log.error(traceback.format_exc())
316 316 h.flash(_('error occurred during update of user %s') \
317 317 % form_result.get('username'), category='error')
318 318
319 319 return redirect(url('my_account'))
320 320
321 321 @NotAnonymous()
322 322 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
323 323 def create_repository(self):
324 324 """GET /_admin/create_repository: Form to create a new item"""
325 325
326 326 c.repo_groups = [('', '')]
327 327 parents_link = lambda k: h.literal('&raquo;'.join(
328 328 map(lambda k: k.group_name,
329 329 k.parents + [k])
330 330 )
331 331 )
332 332
333 333 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
334 334 x in self.sa.query(Group).all()])
335 c.repo_groups = sorted(c.repo_groups,
336 key=lambda t: t[1].split('&raquo;')[0])
335 337 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
336 338
337 339 new_repo = request.GET.get('repo', '')
338 340 c.new_repo = repo_name_slug(new_repo)
339 341
340 342 return render('admin/repos/repo_add_create_repository.html')
341 343
342 344 def get_hg_ui_settings(self):
343 345 ret = self.sa.query(RhodeCodeUi).all()
344 346
345 347 if not ret:
346 348 raise Exception('Could not get application ui settings !')
347 349 settings = {}
348 350 for each in ret:
349 351 k = each.ui_key
350 352 v = each.ui_value
351 353 if k == '/':
352 354 k = 'root_path'
353 355
354 356 if k.find('.') != -1:
355 357 k = k.replace('.', '_')
356 358
357 359 if each.ui_section == 'hooks':
358 360 v = each.ui_active
359 361
360 362 settings[each.ui_section + '_' + k] = v
361 363
362 364 return settings
@@ -1,81 +1,80 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 from operator import itemgetter
28 28
29 29 from pylons import tmpl_context as c, request
30 30 from paste.httpexceptions import HTTPBadRequest
31 31
32 32 from rhodecode.lib.auth import LoginRequired
33 33 from rhodecode.lib.base import BaseController, render
34 34 from rhodecode.model.db import Group
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class HomeController(BaseController):
40 40
41 41 @LoginRequired()
42 42 def __before__(self):
43 43 super(HomeController, self).__before__()
44 44
45 45 def index(self):
46 46 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
47 47 current_sort = request.GET.get('sort', 'name')
48 48 current_sort_slug = current_sort.replace('-', '')
49 49
50 50 if current_sort_slug not in sortables:
51 51 c.sort_by = 'name'
52 52 current_sort_slug = c.sort_by
53 53 else:
54 54 c.sort_by = current_sort
55 55 c.sort_slug = current_sort_slug
56 56
57 57 sort_key = current_sort_slug + '_sort'
58 58
59 59 if c.sort_by.startswith('-'):
60 60 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
61 61 reverse=True)
62 62 else:
63 63 c.repos_list = sorted(c.cached_repo_list, key=itemgetter(sort_key),
64 64 reverse=False)
65 65
66 66 c.repo_cnt = len(c.repos_list)
67 67
68 68
69 c.groups = self.sa.query(Group)\
70 .filter(Group.group_parent_id == None).all()
69 c.groups = Group.query().filter(Group.group_parent_id == None).all()
71 70
72 71
73 72 return render('/index.html')
74 73
75 74 def repo_switcher(self):
76 75 if request.is_xhr:
77 76 c.repos_list = sorted(c.cached_repo_list,
78 77 key=itemgetter('name_sort'), reverse=False)
79 78 return render('/repo_switcher_list.html')
80 79 else:
81 80 return HTTPBadRequest()
@@ -1,547 +1,552 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 from datetime import date
30 30
31 31 from sqlalchemy import *
32 32 from sqlalchemy.exc import DatabaseError
33 33 from sqlalchemy.orm import relationship, backref
34 34 from sqlalchemy.orm.interfaces import MapperExtension
35 35
36 36 from rhodecode.lib import str2bool
37 37 from rhodecode.model.meta import Base, Session
38 38 from rhodecode.model.caching_query import FromCache
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 #==============================================================================
43 43 # MAPPER EXTENSIONS
44 44 #==============================================================================
45 45
46 46 class RepositoryMapper(MapperExtension):
47 47 def after_update(self, mapper, connection, instance):
48 48 pass
49 49
50 50
51 51 class RhodeCodeSettings(Base):
52 52 __tablename__ = 'rhodecode_settings'
53 53 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
54 54 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
55 55 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
56 56 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
57 57
58 58 def __init__(self, k='', v=''):
59 59 self.app_settings_name = k
60 60 self.app_settings_value = v
61 61
62 62 def __repr__(self):
63 63 return "<%s('%s:%s')>" % (self.__class__.__name__,
64 64 self.app_settings_name, self.app_settings_value)
65 65
66 66
67 67 @classmethod
68 68 def get_by_name(cls, ldap_key):
69 69 return Session.query(cls)\
70 70 .filter(cls.app_settings_name == ldap_key).scalar()
71 71
72 72 @classmethod
73 73 def get_app_settings(cls, cache=False):
74 74
75 75 ret = Session.query(cls)
76 76
77 77 if cache:
78 78 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
79 79
80 80 if not ret:
81 81 raise Exception('Could not get application settings !')
82 82 settings = {}
83 83 for each in ret:
84 84 settings['rhodecode_' + each.app_settings_name] = \
85 85 each.app_settings_value
86 86
87 87 return settings
88 88
89 89 @classmethod
90 90 def get_ldap_settings(cls, cache=False):
91 91 ret = Session.query(cls)\
92 92 .filter(cls.app_settings_name.startswith('ldap_'))\
93 93 .all()
94 94 fd = {}
95 95 for row in ret:
96 96 fd.update({row.app_settings_name:row.app_settings_value})
97 97 return fd
98 98
99 99
100 100 class RhodeCodeUi(Base):
101 101 __tablename__ = 'rhodecode_ui'
102 102 __table_args__ = {'useexisting':True}
103 103 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
104 104 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
105 105 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 106 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 107 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
108 108
109 109
110 @classmethod
111 def get_by_key(cls, key):
112 return Session.query(cls).filter(cls.ui_key == key)
113
114
110 115 class User(Base):
111 116 __tablename__ = 'users'
112 117 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
113 118 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
114 119 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
115 120 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
116 121 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
117 122 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
118 123 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
119 124 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
120 125 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
121 126 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
122 127 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
123 128 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
124 129
125 130 user_log = relationship('UserLog', cascade='all')
126 131 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
127 132
128 133 repositories = relationship('Repository')
129 134 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
130 135 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
131 136
132 137 group_member = relationship('UsersGroupMember', cascade='all')
133 138
134 139 @property
135 140 def full_contact(self):
136 141 return '%s %s <%s>' % (self.name, self.lastname, self.email)
137 142
138 143 @property
139 144 def short_contact(self):
140 145 return '%s %s' % (self.name, self.lastname)
141 146
142 147
143 148 @property
144 149 def is_admin(self):
145 150 return self.admin
146 151
147 152 def __repr__(self):
148 153 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
149 154 self.user_id, self.username)
150 155
151 156 @classmethod
152 157 def by_username(cls, username):
153 158 return Session.query(cls).filter(cls.username == username).one()
154 159
155 160
156 161 def update_lastlogin(self):
157 162 """Update user lastlogin"""
158 163
159 164 try:
160 165 session = Session.object_session(self)
161 166 self.last_login = datetime.datetime.now()
162 167 session.add(self)
163 168 session.commit()
164 169 log.debug('updated user %s lastlogin', self.username)
165 170 except (DatabaseError,):
166 171 session.rollback()
167 172
168 173
169 174 class UserLog(Base):
170 175 __tablename__ = 'user_logs'
171 176 __table_args__ = {'useexisting':True}
172 177 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
173 178 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
174 179 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
175 180 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
176 181 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
177 182 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
178 183 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
179 184
180 185 @property
181 186 def action_as_day(self):
182 187 return date(*self.action_date.timetuple()[:3])
183 188
184 189 user = relationship('User')
185 190 repository = relationship('Repository')
186 191
187 192
188 193 class UsersGroup(Base):
189 194 __tablename__ = 'users_groups'
190 195 __table_args__ = {'useexisting':True}
191 196
192 197 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
193 198 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
194 199 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
195 200
196 201 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
197 202
198 203
199 204 @classmethod
200 205 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
201 206 if case_insensitive:
202 207 gr = Session.query(cls)\
203 208 .filter(cls.users_group_name.ilike(group_name))
204 209 else:
205 210 gr = Session.query(UsersGroup)\
206 211 .filter(UsersGroup.users_group_name == group_name)
207 212 if cache:
208 213 gr = gr.options(FromCache("sql_cache_short",
209 214 "get_user_%s" % group_name))
210 215 return gr.scalar()
211 216
212 217 class UsersGroupMember(Base):
213 218 __tablename__ = 'users_groups_members'
214 219 __table_args__ = {'useexisting':True}
215 220
216 221 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
217 222 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
218 223 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
219 224
220 225 user = relationship('User', lazy='joined')
221 226 users_group = relationship('UsersGroup')
222 227
223 228 def __init__(self, gr_id='', u_id=''):
224 229 self.users_group_id = gr_id
225 230 self.user_id = u_id
226 231
227 232 class Repository(Base):
228 233 __tablename__ = 'repositories'
229 234 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
230 235 __mapper_args__ = {'extension':RepositoryMapper()}
231 236
232 237 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 238 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
234 239 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
235 240 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
236 241 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
237 242 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
238 243 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
239 244 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
240 245 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 246 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
242 247
243 248 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
244 249 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
245 250
246 251
247 252 user = relationship('User')
248 253 fork = relationship('Repository', remote_side=repo_id)
249 254 group = relationship('Group')
250 255 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
251 256 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
252 257 stats = relationship('Statistics', cascade='all', uselist=False)
253 258
254 259 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
255 260
256 261 logs = relationship('UserLog', cascade='all')
257 262
258 263 def __repr__(self):
259 264 return "<%s('%s:%s')>" % (self.__class__.__name__,
260 265 self.repo_id, self.repo_name)
261 266
262 267 @classmethod
263 268 def by_repo_name(cls, repo_name):
264 269 return Session.query(cls).filter(cls.repo_name == repo_name).one()
265 270
266 271
267 272 @classmethod
268 273 def get_repo_forks(cls, repo_id):
269 274 return Session.query(cls).filter(Repository.fork_id == repo_id)
270 275
271 276 @property
272 277 def just_name(self):
273 278 return self.repo_name.split(os.sep)[-1]
274 279
275 280 @property
276 281 def groups_with_parents(self):
277 282 groups = []
278 283 if self.group is None:
279 284 return groups
280 285
281 286 cur_gr = self.group
282 287 groups.insert(0, cur_gr)
283 288 while 1:
284 289 gr = getattr(cur_gr, 'parent_group', None)
285 290 cur_gr = cur_gr.parent_group
286 291 if gr is None:
287 292 break
288 293 groups.insert(0, gr)
289 294
290 295 return groups
291 296
292 297 @property
293 298 def groups_and_repo(self):
294 299 return self.groups_with_parents, self.just_name
295 300
296 301
297 302 class Group(Base):
298 303 __tablename__ = 'groups'
299 __table_args__ = (UniqueConstraint('group_name'), {'useexisting':True},)
304 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'), {'useexisting':True},)
300 305 __mapper_args__ = {'order_by':'group_name'}
301 306
302 307 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
303 308 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
304 309 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
305 310 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 311
307 312 parent_group = relationship('Group', remote_side=group_id)
308 313
309 314
310 315 def __init__(self, group_name='', parent_group=None):
311 316 self.group_name = group_name
312 317 self.parent_group = parent_group
313 318
314 319 def __repr__(self):
315 320 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
316 321 self.group_name)
317 322
318 323 @property
319 324 def parents(self):
320 325 groups = []
321 326 if self.parent_group is None:
322 327 return groups
323 328 cur_gr = self.parent_group
324 329 groups.insert(0, cur_gr)
325 330 while 1:
326 331 gr = getattr(cur_gr, 'parent_group', None)
327 332 cur_gr = cur_gr.parent_group
328 333 if gr is None:
329 334 break
330 335 groups.insert(0, gr)
331 336 return groups
332 337
333 338
334 339 @property
335 340 def full_path(self):
336 341 return '/'.join([g.group_name for g in self.parents] +
337 342 [self.group_name])
338 343
339 344 @property
340 345 def repositories(self):
341 346 return Session.query(Repository).filter(Repository.group == self)
342 347
343 348 class Permission(Base):
344 349 __tablename__ = 'permissions'
345 350 __table_args__ = {'useexisting':True}
346 351 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 352 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
348 353 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
349 354
350 355 def __repr__(self):
351 356 return "<%s('%s:%s')>" % (self.__class__.__name__,
352 357 self.permission_id, self.permission_name)
353 358
354 359 @classmethod
355 360 def get_by_key(cls, key):
356 361 return Session.query(cls).filter(cls.permission_name == key).scalar()
357 362
358 363 class RepoToPerm(Base):
359 364 __tablename__ = 'repo_to_perm'
360 365 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
361 366 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
362 367 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
363 368 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
364 369 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
365 370
366 371 user = relationship('User')
367 372 permission = relationship('Permission')
368 373 repository = relationship('Repository')
369 374
370 375 class UserToPerm(Base):
371 376 __tablename__ = 'user_to_perm'
372 377 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
373 378 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
374 379 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
375 380 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
376 381
377 382 user = relationship('User')
378 383 permission = relationship('Permission')
379 384
380 385 @classmethod
381 386 def has_perm(cls, user_id, perm):
382 387 if not isinstance(perm, Permission):
383 388 raise Exception('perm needs to be an instance of Permission class')
384 389
385 390 return Session.query(cls).filter(cls.user_id == user_id)\
386 391 .filter(cls.permission == perm).scalar() is not None
387 392
388 393 @classmethod
389 394 def grant_perm(cls, user_id, perm):
390 395 if not isinstance(perm, Permission):
391 396 raise Exception('perm needs to be an instance of Permission class')
392 397
393 398 new = cls()
394 399 new.user_id = user_id
395 400 new.permission = perm
396 401 try:
397 402 Session.add(new)
398 403 Session.commit()
399 404 except:
400 405 Session.rollback()
401 406
402 407
403 408 @classmethod
404 409 def revoke_perm(cls, user_id, perm):
405 410 if not isinstance(perm, Permission):
406 411 raise Exception('perm needs to be an instance of Permission class')
407 412
408 413 try:
409 414 Session.query(cls).filter(cls.user_id == user_id)\
410 415 .filter(cls.permission == perm).delete()
411 416 Session.commit()
412 417 except:
413 418 Session.rollback()
414 419
415 420 class UsersGroupRepoToPerm(Base):
416 421 __tablename__ = 'users_group_repo_to_perm'
417 422 __table_args__ = (UniqueConstraint('users_group_id', 'permission_id'), {'useexisting':True})
418 423 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
419 424 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
420 425 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
421 426 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
422 427
423 428 users_group = relationship('UsersGroup')
424 429 permission = relationship('Permission')
425 430 repository = relationship('Repository')
426 431
427 432
428 433 class UsersGroupToPerm(Base):
429 434 __tablename__ = 'users_group_to_perm'
430 435 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
431 436 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
432 437 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
433 438
434 439 users_group = relationship('UsersGroup')
435 440 permission = relationship('Permission')
436 441
437 442
438 443 @classmethod
439 444 def has_perm(cls, users_group_id, perm):
440 445 if not isinstance(perm, Permission):
441 446 raise Exception('perm needs to be an instance of Permission class')
442 447
443 448 return Session.query(cls).filter(cls.users_group_id ==
444 449 users_group_id)\
445 450 .filter(cls.permission == perm)\
446 451 .scalar() is not None
447 452
448 453 @classmethod
449 454 def grant_perm(cls, users_group_id, perm):
450 455 if not isinstance(perm, Permission):
451 456 raise Exception('perm needs to be an instance of Permission class')
452 457
453 458 new = cls()
454 459 new.users_group_id = users_group_id
455 460 new.permission = perm
456 461 try:
457 462 Session.add(new)
458 463 Session.commit()
459 464 except:
460 465 Session.rollback()
461 466
462 467
463 468 @classmethod
464 469 def revoke_perm(cls, users_group_id, perm):
465 470 if not isinstance(perm, Permission):
466 471 raise Exception('perm needs to be an instance of Permission class')
467 472
468 473 try:
469 474 Session.query(cls).filter(cls.users_group_id == users_group_id)\
470 475 .filter(cls.permission == perm).delete()
471 476 Session.commit()
472 477 except:
473 478 Session.rollback()
474 479
475 480
476 481 class GroupToPerm(Base):
477 482 __tablename__ = 'group_to_perm'
478 483 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'useexisting':True})
479 484
480 485 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 486 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
482 487 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
483 488 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
484 489
485 490 user = relationship('User')
486 491 permission = relationship('Permission')
487 492 group = relationship('Group')
488 493
489 494 class Statistics(Base):
490 495 __tablename__ = 'statistics'
491 496 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
492 497 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
493 498 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
494 499 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
495 500 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
496 501 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
497 502 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
498 503
499 504 repository = relationship('Repository', single_parent=True)
500 505
501 506 class UserFollowing(Base):
502 507 __tablename__ = 'user_followings'
503 508 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
504 509 UniqueConstraint('user_id', 'follows_user_id')
505 510 , {'useexisting':True})
506 511
507 512 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 513 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
509 514 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
510 515 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
511 516 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
512 517
513 518 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
514 519
515 520 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
516 521 follows_repository = relationship('Repository', order_by='Repository.repo_name')
517 522
518 523
519 524
520 525 @classmethod
521 526 def get_repo_followers(cls, repo_id):
522 527 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
523 528
524 529 class CacheInvalidation(Base):
525 530 __tablename__ = 'cache_invalidation'
526 531 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
527 532 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
528 533 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
529 534 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
530 535 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
531 536
532 537
533 538 def __init__(self, cache_key, cache_args=''):
534 539 self.cache_key = cache_key
535 540 self.cache_args = cache_args
536 541 self.cache_active = False
537 542
538 543 def __repr__(self):
539 544 return "<%s('%s:%s')>" % (self.__class__.__name__,
540 545 self.cache_id, self.cache_key)
541 546
542 547 class DbMigrateVersion(Base):
543 548 __tablename__ = 'db_migrate_version'
544 549 __table_args__ = {'useexisting':True}
545 550 repository_id = Column('repository_id', String(250), primary_key=True)
546 551 repository_path = Column('repository_path', Text)
547 552 version = Column('version', Integer)
@@ -1,619 +1,644 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 os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.lib.utils import repo_name_slug
36 36 from rhodecode.lib.auth import authenticate, get_crypt_password
37 37 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.model import meta
39 38 from rhodecode.model.user import UserModel
40 39 from rhodecode.model.repo import RepoModel
41 40 from rhodecode.model.db import User, UsersGroup, Group
42 41 from rhodecode import BACKENDS
43 42
44 43 log = logging.getLogger(__name__)
45 44
46 45 #this is needed to translate the messages using _() in validators
47 46 class State_obj(object):
48 47 _ = staticmethod(_)
49 48
50 49 #==============================================================================
51 50 # VALIDATORS
52 51 #==============================================================================
53 52 class ValidAuthToken(formencode.validators.FancyValidator):
54 53 messages = {'invalid_token':_('Token mismatch')}
55 54
56 55 def validate_python(self, value, state):
57 56
58 57 if value != authentication_token():
59 58 raise formencode.Invalid(self.message('invalid_token', state,
60 59 search_number=value), value, state)
61 60
62 61 def ValidUsername(edit, old_data):
63 62 class _ValidUsername(formencode.validators.FancyValidator):
64 63
65 64 def validate_python(self, value, state):
66 65 if value in ['default', 'new_user']:
67 66 raise formencode.Invalid(_('Invalid username'), value, state)
68 67 #check if user is unique
69 68 old_un = None
70 69 if edit:
71 70 old_un = UserModel().get(old_data.get('user_id')).username
72 71
73 72 if old_un != value or not edit:
74 73 if UserModel().get_by_username(value, cache=False,
75 74 case_insensitive=True):
76 75 raise formencode.Invalid(_('This username already '
77 76 'exists') , value, state)
78 77
79 78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
80 79 raise formencode.Invalid(_('Username may only contain '
81 80 'alphanumeric characters '
82 81 'underscores, periods or dashes '
83 82 'and must begin with alphanumeric '
84 83 'character'), value, state)
85 84
86 85 return _ValidUsername
87 86
88 87
89 88 def ValidUsersGroup(edit, old_data):
90 89
91 90 class _ValidUsersGroup(formencode.validators.FancyValidator):
92 91
93 92 def validate_python(self, value, state):
94 93 if value in ['default']:
95 94 raise formencode.Invalid(_('Invalid group name'), value, state)
96 95 #check if group is unique
97 96 old_ugname = None
98 97 if edit:
99 98 old_ugname = UsersGroup.get(
100 99 old_data.get('users_group_id')).users_group_name
101 100
102 101 if old_ugname != value or not edit:
103 102 if UsersGroup.get_by_group_name(value, cache=False,
104 103 case_insensitive=True):
105 104 raise formencode.Invalid(_('This users group '
106 105 'already exists') , value,
107 106 state)
108 107
109 108
110 109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
111 110 raise formencode.Invalid(_('Group name may only contain '
112 111 'alphanumeric characters '
113 112 'underscores, periods or dashes '
114 113 'and must begin with alphanumeric '
115 114 'character'), value, state)
116 115
117 116 return _ValidUsersGroup
118 117
119 118
119 def ValidReposGroup(edit, old_data):
120
121 class _ValidReposGroup(formencode.validators.FancyValidator):
122
123 def validate_python(self, value, state):
124 #TODO WRITE VALIDATIONS
125 group_name = value.get('repos_group_name')
126 parent_id = value.get('repos_group_parent')
127
128 # slugify repo group just in case :)
129 slug = repo_name_slug(group_name)
130
131 # check filesystem
132 gr = Group.query().filter(Group.group_name == slug)\
133 .filter(Group.group_parent_id == parent_id).scalar()
134
135 if gr:
136 e_dict = {'repos_group_name':_('This group already exists')}
137 raise formencode.Invalid('', value, state,
138 error_dict=e_dict)
139 return _ValidReposGroup
120 140
121 141 class ValidPassword(formencode.validators.FancyValidator):
122 142
123 143 def to_python(self, value, state):
124 144
125 145 if value:
126 146
127 147 if value.get('password'):
128 148 try:
129 149 value['password'] = get_crypt_password(value['password'])
130 150 except UnicodeEncodeError:
131 151 e_dict = {'password':_('Invalid characters in password')}
132 152 raise formencode.Invalid('', value, state, error_dict=e_dict)
133 153
134 154 if value.get('password_confirmation'):
135 155 try:
136 156 value['password_confirmation'] = \
137 157 get_crypt_password(value['password_confirmation'])
138 158 except UnicodeEncodeError:
139 159 e_dict = {'password_confirmation':_('Invalid characters in password')}
140 160 raise formencode.Invalid('', value, state, error_dict=e_dict)
141 161
142 162 if value.get('new_password'):
143 163 try:
144 164 value['new_password'] = \
145 165 get_crypt_password(value['new_password'])
146 166 except UnicodeEncodeError:
147 167 e_dict = {'new_password':_('Invalid characters in password')}
148 168 raise formencode.Invalid('', value, state, error_dict=e_dict)
149 169
150 170 return value
151 171
152 172 class ValidPasswordsMatch(formencode.validators.FancyValidator):
153 173
154 174 def validate_python(self, value, state):
155 175
156 176 if value['password'] != value['password_confirmation']:
157 177 e_dict = {'password_confirmation':
158 178 _('Password do not match')}
159 179 raise formencode.Invalid('', value, state, error_dict=e_dict)
160 180
161 181 class ValidAuth(formencode.validators.FancyValidator):
162 182 messages = {
163 183 'invalid_password':_('invalid password'),
164 184 'invalid_login':_('invalid user name'),
165 185 'disabled_account':_('Your account is disabled')
166 186
167 187 }
168 188 #error mapping
169 189 e_dict = {'username':messages['invalid_login'],
170 190 'password':messages['invalid_password']}
171 191 e_dict_disable = {'username':messages['disabled_account']}
172 192
173 193 def validate_python(self, value, state):
174 194 password = value['password']
175 195 username = value['username']
176 196 user = UserModel().get_by_username(username)
177 197
178 198 if authenticate(username, password):
179 199 return value
180 200 else:
181 201 if user and user.active is False:
182 202 log.warning('user %s is disabled', username)
183 203 raise formencode.Invalid(self.message('disabled_account',
184 204 state=State_obj),
185 205 value, state,
186 206 error_dict=self.e_dict_disable)
187 207 else:
188 208 log.warning('user %s not authenticated', username)
189 209 raise formencode.Invalid(self.message('invalid_password',
190 210 state=State_obj), value, state,
191 211 error_dict=self.e_dict)
192 212
193 213 class ValidRepoUser(formencode.validators.FancyValidator):
194 214
195 215 def to_python(self, value, state):
196 sa = meta.Session()
197 216 try:
198 self.user_db = sa.query(User)\
217 self.user_db = User.query()\
199 218 .filter(User.active == True)\
200 219 .filter(User.username == value).one()
201 220 except Exception:
202 221 raise formencode.Invalid(_('This username is not valid'),
203 222 value, state)
204 finally:
205 meta.Session.remove()
206
207 223 return value
208 224
209 225 def ValidRepoName(edit, old_data):
210 226 class _ValidRepoName(formencode.validators.FancyValidator):
211 227 def to_python(self, value, state):
212 228
213 229 repo_name = value.get('repo_name')
214 230
215 231 slug = repo_name_slug(repo_name)
216 232 if slug in ['_admin', '']:
217 233 e_dict = {'repo_name': _('This repository name is disallowed')}
218 234 raise formencode.Invalid('', value, state, error_dict=e_dict)
219 235
220 236
221 237 if value.get('repo_group'):
222 238 gr = Group.get(value.get('repo_group'))
223 239 group_path = gr.full_path
224 240 # value needs to be aware of group name
241 # it has to use '/'
225 242 repo_name_full = group_path + '/' + repo_name
226 243 else:
227 244 group_path = ''
228 245 repo_name_full = repo_name
229 246
230 247
231 248 value['repo_name_full'] = repo_name_full
232 249 if old_data.get('repo_name') != repo_name_full or not edit:
233 250
234 251 if group_path != '':
235 252 if RepoModel().get_by_repo_name(repo_name_full,):
236 253 e_dict = {'repo_name':_('This repository already '
237 254 'exists in group "%s"') %
238 255 gr.group_name}
239 256 raise formencode.Invalid('', value, state,
240 257 error_dict=e_dict)
241 258
242 259 else:
243 260 if RepoModel().get_by_repo_name(repo_name_full):
244 261 e_dict = {'repo_name':_('This repository '
245 262 'already exists')}
246 263 raise formencode.Invalid('', value, state,
247 264 error_dict=e_dict)
248 265 return value
249 266
250 267
251 268 return _ValidRepoName
252 269
253 def SlugifyRepo():
254 class _SlugifyRepo(formencode.validators.FancyValidator):
270 def SlugifyName():
271 class _SlugifyName(formencode.validators.FancyValidator):
255 272
256 273 def to_python(self, value, state):
257 274 return repo_name_slug(value)
258 275
259 return _SlugifyRepo
276 return _SlugifyName
260 277
261 278 def ValidCloneUri():
262 279 from mercurial.httprepo import httprepository, httpsrepository
263 280 from rhodecode.lib.utils import make_ui
264 281
265 282 class _ValidCloneUri(formencode.validators.FancyValidator):
266 283
267 284 def to_python(self, value, state):
268 285 if not value:
269 286 pass
270 287 elif value.startswith('https'):
271 288 try:
272 289 httpsrepository(make_ui('db'), value).capabilities
273 290 except Exception, e:
274 291 log.error(traceback.format_exc())
275 292 raise formencode.Invalid(_('invalid clone url'), value,
276 293 state)
277 294 elif value.startswith('http'):
278 295 try:
279 296 httprepository(make_ui('db'), value).capabilities
280 297 except Exception, e:
281 298 log.error(traceback.format_exc())
282 299 raise formencode.Invalid(_('invalid clone url'), value,
283 300 state)
284 301 else:
285 302 raise formencode.Invalid(_('Invalid clone url, provide a '
286 303 'valid clone http\s url'), value,
287 304 state)
288 305 return value
289 306
290 307 return _ValidCloneUri
291 308
292 309 def ValidForkType(old_data):
293 310 class _ValidForkType(formencode.validators.FancyValidator):
294 311
295 312 def to_python(self, value, state):
296 313 if old_data['repo_type'] != value:
297 314 raise formencode.Invalid(_('Fork have to be the same '
298 315 'type as original'), value, state)
299 316 return value
300 317 return _ValidForkType
301 318
302 319 class ValidPerms(formencode.validators.FancyValidator):
303 320 messages = {'perm_new_member_name':_('This username or users group name'
304 321 ' is not valid')}
305 322
306 323 def to_python(self, value, state):
307 324 perms_update = []
308 325 perms_new = []
309 326 #build a list of permission to update and new permission to create
310 327 for k, v in value.items():
311 328 #means new added member to permissions
312 329 if k.startswith('perm_new_member'):
313 330 new_perm = value.get('perm_new_member', False)
314 331 new_member = value.get('perm_new_member_name', False)
315 332 new_type = value.get('perm_new_member_type')
316 333
317 334 if new_member and new_perm:
318 335 if (new_member, new_perm, new_type) not in perms_new:
319 336 perms_new.append((new_member, new_perm, new_type))
320 337 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
321 338 member = k[7:]
322 339 t = {'u':'user',
323 340 'g':'users_group'}[k[0]]
324 341 if member == 'default':
325 342 if value['private']:
326 343 #set none for default when updating to private repo
327 344 v = 'repository.none'
328 345 perms_update.append((member, v, t))
329 346
330 347 value['perms_updates'] = perms_update
331 348 value['perms_new'] = perms_new
332 349
333 350 #update permissions
334 sa = meta.Session
335 351 for k, v, t in perms_new:
336 352 try:
337 353 if t is 'user':
338 self.user_db = sa.query(User)\
354 self.user_db = User.query()\
339 355 .filter(User.active == True)\
340 356 .filter(User.username == k).one()
341 357 if t is 'users_group':
342 self.user_db = sa.query(UsersGroup)\
358 self.user_db = UsersGroup.query()\
343 359 .filter(UsersGroup.users_group_active == True)\
344 360 .filter(UsersGroup.users_group_name == k).one()
345 361
346 362 except Exception:
347 363 msg = self.message('perm_new_member_name',
348 364 state=State_obj)
349 365 raise formencode.Invalid(msg, value, state,
350 366 error_dict={'perm_new_member_name':msg})
351 367 return value
352 368
353 369 class ValidSettings(formencode.validators.FancyValidator):
354 370
355 371 def to_python(self, value, state):
356 372 #settings form can't edit user
357 373 if value.has_key('user'):
358 374 del['value']['user']
359 375
360 376 return value
361 377
362 378 class ValidPath(formencode.validators.FancyValidator):
363 379 def to_python(self, value, state):
364 380
365 381 if not os.path.isdir(value):
366 382 msg = _('This is not a valid path')
367 383 raise formencode.Invalid(msg, value, state,
368 384 error_dict={'paths_root_path':msg})
369 385 return value
370 386
371 387 def UniqSystemEmail(old_data):
372 388 class _UniqSystemEmail(formencode.validators.FancyValidator):
373 389 def to_python(self, value, state):
374 390 value = value.lower()
375 391 if old_data.get('email') != value:
376 sa = meta.Session()
377 try:
378 user = sa.query(User).filter(User.email == value).scalar()
379 if user:
380 raise formencode.Invalid(_("This e-mail address is already taken") ,
381 value, state)
382 finally:
383 meta.Session.remove()
384
392 user = User.query().filter(User.email == value).scalar()
393 if user:
394 raise formencode.Invalid(
395 _("This e-mail address is already taken"),
396 value, state)
385 397 return value
386 398
387 399 return _UniqSystemEmail
388 400
389 401 class ValidSystemEmail(formencode.validators.FancyValidator):
390 402 def to_python(self, value, state):
391 403 value = value.lower()
392 sa = meta.Session
393 try:
394 user = sa.query(User).filter(User.email == value).scalar()
395 if user is None:
396 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
397 value, state)
398 finally:
399 meta.Session.remove()
404 user = User.query().filter(User.email == value).scalar()
405 if user is None:
406 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
407 value, state)
400 408
401 409 return value
402 410
403 411 class LdapLibValidator(formencode.validators.FancyValidator):
404 412
405 413 def to_python(self, value, state):
406 414
407 415 try:
408 416 import ldap
409 417 except ImportError:
410 418 raise LdapImportError
411 419 return value
412 420
413 421 class AttrLoginValidator(formencode.validators.FancyValidator):
414 422
415 423 def to_python(self, value, state):
416 424
417 425 if not value or not isinstance(value, (str, unicode)):
418 426 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
419 427 "must be specified - this is the name "
420 428 "of the attribute that is equivalent "
421 429 "to 'username'"),
422 430 value, state)
423 431
424 432 return value
425 433
426 434 #===============================================================================
427 435 # FORMS
428 436 #===============================================================================
429 437 class LoginForm(formencode.Schema):
430 438 allow_extra_fields = True
431 439 filter_extra_fields = True
432 440 username = UnicodeString(
433 441 strip=True,
434 442 min=1,
435 443 not_empty=True,
436 444 messages={
437 445 'empty':_('Please enter a login'),
438 446 'tooShort':_('Enter a value %(min)i characters long or more')}
439 447 )
440 448
441 449 password = UnicodeString(
442 450 strip=True,
443 451 min=6,
444 452 not_empty=True,
445 453 messages={
446 454 'empty':_('Please enter a password'),
447 455 'tooShort':_('Enter %(min)i characters or more')}
448 456 )
449 457
450 458
451 459 #chained validators have access to all data
452 460 chained_validators = [ValidAuth]
453 461
454 462 def UserForm(edit=False, old_data={}):
455 463 class _UserForm(formencode.Schema):
456 464 allow_extra_fields = True
457 465 filter_extra_fields = True
458 466 username = All(UnicodeString(strip=True, min=1, not_empty=True),
459 467 ValidUsername(edit, old_data))
460 468 if edit:
461 469 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
462 470 admin = StringBoolean(if_missing=False)
463 471 else:
464 472 password = All(UnicodeString(strip=True, min=6, not_empty=True))
465 473 active = StringBoolean(if_missing=False)
466 474 name = UnicodeString(strip=True, min=1, not_empty=True)
467 475 lastname = UnicodeString(strip=True, min=1, not_empty=True)
468 476 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
469 477
470 478 chained_validators = [ValidPassword]
471 479
472 480 return _UserForm
473 481
474 482
475 483 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
476 484 class _UsersGroupForm(formencode.Schema):
477 485 allow_extra_fields = True
478 486 filter_extra_fields = True
479 487
480 488 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
481 489 ValidUsersGroup(edit, old_data))
482 490
483 491 users_group_active = StringBoolean(if_missing=False)
484 492
485 493 if edit:
486 494 users_group_members = OneOf(available_members, hideList=False,
487 495 testValueList=True,
488 496 if_missing=None, not_empty=False)
489 497
490 498 return _UsersGroupForm
491 499
500 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
501 class _ReposGroupForm(formencode.Schema):
502 allow_extra_fields = True
503 filter_extra_fields = True
504
505 repos_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
506 SlugifyName())
507 repos_group_description = UnicodeString(strip=True, min=1,
508 not_empty=True)
509 repos_group_parent = OneOf(available_groups, hideList=False,
510 testValueList=True,
511 if_missing=None, not_empty=False)
512
513 chained_validators = [ValidReposGroup(edit, old_data)]
514
515 return _ReposGroupForm
516
492 517 def RegisterForm(edit=False, old_data={}):
493 518 class _RegisterForm(formencode.Schema):
494 519 allow_extra_fields = True
495 520 filter_extra_fields = True
496 521 username = All(ValidUsername(edit, old_data),
497 522 UnicodeString(strip=True, min=1, not_empty=True))
498 523 password = All(UnicodeString(strip=True, min=6, not_empty=True))
499 524 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
500 525 active = StringBoolean(if_missing=False)
501 526 name = UnicodeString(strip=True, min=1, not_empty=True)
502 527 lastname = UnicodeString(strip=True, min=1, not_empty=True)
503 528 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
504 529
505 530 chained_validators = [ValidPasswordsMatch, ValidPassword]
506 531
507 532 return _RegisterForm
508 533
509 534 def PasswordResetForm():
510 535 class _PasswordResetForm(formencode.Schema):
511 536 allow_extra_fields = True
512 537 filter_extra_fields = True
513 538 email = All(ValidSystemEmail(), Email(not_empty=True))
514 539 return _PasswordResetForm
515 540
516 541 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
517 542 repo_groups=[]):
518 543 class _RepoForm(formencode.Schema):
519 544 allow_extra_fields = True
520 545 filter_extra_fields = False
521 546 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
522 SlugifyRepo())
547 SlugifyName())
523 548 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
524 549 ValidCloneUri()())
525 550 repo_group = OneOf(repo_groups, hideList=True)
526 551 repo_type = OneOf(supported_backends)
527 552 description = UnicodeString(strip=True, min=1, not_empty=True)
528 553 private = StringBoolean(if_missing=False)
529 554 enable_statistics = StringBoolean(if_missing=False)
530 555 enable_downloads = StringBoolean(if_missing=False)
531 556
532 557 if edit:
533 558 #this is repo owner
534 559 user = All(UnicodeString(not_empty=True), ValidRepoUser)
535 560
536 561 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
537 562 return _RepoForm
538 563
539 564 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
540 565 class _RepoForkForm(formencode.Schema):
541 566 allow_extra_fields = True
542 567 filter_extra_fields = False
543 568 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
544 SlugifyRepo())
569 SlugifyName())
545 570 description = UnicodeString(strip=True, min=1, not_empty=True)
546 571 private = StringBoolean(if_missing=False)
547 572 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
548 573 return _RepoForkForm
549 574
550 575 def RepoSettingsForm(edit=False, old_data={}):
551 576 class _RepoForm(formencode.Schema):
552 577 allow_extra_fields = True
553 578 filter_extra_fields = False
554 579 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
555 SlugifyRepo())
580 SlugifyName())
556 581 description = UnicodeString(strip=True, min=1, not_empty=True)
557 582 private = StringBoolean(if_missing=False)
558 583
559 584 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
560 585 return _RepoForm
561 586
562 587
563 588 def ApplicationSettingsForm():
564 589 class _ApplicationSettingsForm(formencode.Schema):
565 590 allow_extra_fields = True
566 591 filter_extra_fields = False
567 592 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
568 593 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
569 594 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
570 595
571 596 return _ApplicationSettingsForm
572 597
573 598 def ApplicationUiSettingsForm():
574 599 class _ApplicationUiSettingsForm(formencode.Schema):
575 600 allow_extra_fields = True
576 601 filter_extra_fields = False
577 602 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
578 603 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
579 604 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
580 605 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
581 606 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
582 607 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
583 608
584 609 return _ApplicationUiSettingsForm
585 610
586 611 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
587 612 class _DefaultPermissionsForm(formencode.Schema):
588 613 allow_extra_fields = True
589 614 filter_extra_fields = True
590 615 overwrite_default = StringBoolean(if_missing=False)
591 616 anonymous = OneOf(['True', 'False'], if_missing=False)
592 617 default_perm = OneOf(perms_choices)
593 618 default_register = OneOf(register_choices)
594 619 default_create = OneOf(create_choices)
595 620
596 621 return _DefaultPermissionsForm
597 622
598 623
599 624 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
600 625 class _LdapSettingsForm(formencode.Schema):
601 626 allow_extra_fields = True
602 627 filter_extra_fields = True
603 628 pre_validators = [LdapLibValidator]
604 629 ldap_active = StringBoolean(if_missing=False)
605 630 ldap_host = UnicodeString(strip=True,)
606 631 ldap_port = Number(strip=True,)
607 632 ldap_tls_kind = OneOf(tls_kind_choices)
608 633 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
609 634 ldap_dn_user = UnicodeString(strip=True,)
610 635 ldap_dn_pass = UnicodeString(strip=True,)
611 636 ldap_base_dn = UnicodeString(strip=True,)
612 637 ldap_filter = UnicodeString(strip=True,)
613 638 ldap_search_scope = OneOf(search_scope_choices)
614 639 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
615 640 ldap_attr_firstname = UnicodeString(strip=True,)
616 641 ldap_attr_lastname = UnicodeString(strip=True,)
617 642 ldap_attr_email = UnicodeString(strip=True,)
618 643
619 644 return _LdapSettingsForm
@@ -1,2658 +1,2665 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
2 2 border:0;
3 3 outline:0;
4 4 font-size:100%;
5 5 vertical-align:baseline;
6 6 background:transparent;
7 7 margin:0;
8 8 padding:0;
9 9 }
10 10
11 11 body {
12 12 line-height:1;
13 13 height:100%;
14 14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
16 16 font-size:12px;
17 17 color:#000;
18 18 margin:0;
19 19 padding:0;
20 20 }
21 21
22 22 ol,ul {
23 23 list-style:none;
24 24 }
25 25
26 26 blockquote,q {
27 27 quotes:none;
28 28 }
29 29
30 30 blockquote:before,blockquote:after,q:before,q:after {
31 31 content:none;
32 32 }
33 33
34 34 :focus {
35 35 outline:0;
36 36 }
37 37
38 38 del {
39 39 text-decoration:line-through;
40 40 }
41 41
42 42 table {
43 43 border-collapse:collapse;
44 44 border-spacing:0;
45 45 }
46 46
47 47 html {
48 48 height:100%;
49 49 }
50 50
51 51 a {
52 52 color:#003367;
53 53 text-decoration:none;
54 54 cursor:pointer;
55 55 font-weight:700;
56 56 }
57 57
58 58 a:hover {
59 59 color:#316293;
60 60 text-decoration:underline;
61 61 }
62 62
63 63 h1,h2,h3,h4,h5,h6 {
64 64 color:#292929;
65 65 font-weight:700;
66 66 }
67 67
68 68 h1 {
69 69 font-size:22px;
70 70 }
71 71
72 72 h2 {
73 73 font-size:20px;
74 74 }
75 75
76 76 h3 {
77 77 font-size:18px;
78 78 }
79 79
80 80 h4 {
81 81 font-size:16px;
82 82 }
83 83
84 84 h5 {
85 85 font-size:14px;
86 86 }
87 87
88 88 h6 {
89 89 font-size:11px;
90 90 }
91 91
92 92 ul.circle {
93 93 list-style-type:circle;
94 94 }
95 95
96 96 ul.disc {
97 97 list-style-type:disc;
98 98 }
99 99
100 100 ul.square {
101 101 list-style-type:square;
102 102 }
103 103
104 104 ol.lower-roman {
105 105 list-style-type:lower-roman;
106 106 }
107 107
108 108 ol.upper-roman {
109 109 list-style-type:upper-roman;
110 110 }
111 111
112 112 ol.lower-alpha {
113 113 list-style-type:lower-alpha;
114 114 }
115 115
116 116 ol.upper-alpha {
117 117 list-style-type:upper-alpha;
118 118 }
119 119
120 120 ol.decimal {
121 121 list-style-type:decimal;
122 122 }
123 123
124 124 div.color {
125 125 clear:both;
126 126 overflow:hidden;
127 127 position:absolute;
128 128 background:#FFF;
129 129 margin:7px 0 0 60px;
130 130 padding:1px 1px 1px 0;
131 131 }
132 132
133 133 div.color a {
134 134 width:15px;
135 135 height:15px;
136 136 display:block;
137 137 float:left;
138 138 margin:0 0 0 1px;
139 139 padding:0;
140 140 }
141 141
142 142 div.options {
143 143 clear:both;
144 144 overflow:hidden;
145 145 position:absolute;
146 146 background:#FFF;
147 147 margin:7px 0 0 162px;
148 148 padding:0;
149 149 }
150 150
151 151 div.options a {
152 152 height:1%;
153 153 display:block;
154 154 text-decoration:none;
155 155 margin:0;
156 156 padding:3px 8px;
157 157 }
158 158
159 159 .top-left-rounded-corner {
160 160 -webkit-border-top-left-radius: 8px;
161 161 -khtml-border-radius-topleft: 8px;
162 162 -moz-border-radius-topleft: 8px;
163 163 border-top-left-radius: 8px;
164 164 }
165 165
166 166 .top-right-rounded-corner {
167 167 -webkit-border-top-right-radius: 8px;
168 168 -khtml-border-radius-topright: 8px;
169 169 -moz-border-radius-topright: 8px;
170 170 border-top-right-radius: 8px;
171 171 }
172 172
173 173 .bottom-left-rounded-corner {
174 174 -webkit-border-bottom-left-radius: 8px;
175 175 -khtml-border-radius-bottomleft: 8px;
176 176 -moz-border-radius-bottomleft: 8px;
177 177 border-bottom-left-radius: 8px;
178 178 }
179 179
180 180 .bottom-right-rounded-corner {
181 181 -webkit-border-bottom-right-radius: 8px;
182 182 -khtml-border-radius-bottomright: 8px;
183 183 -moz-border-radius-bottomright: 8px;
184 184 border-bottom-right-radius: 8px;
185 185 }
186 186
187 187
188 188 #header {
189 189 margin:0;
190 190 padding:0 10px;
191 191 }
192 192
193 193
194 194 #header ul#logged-user{
195 195 margin-bottom:5px !important;
196 196 -webkit-border-radius: 0px 0px 8px 8px;
197 197 -khtml-border-radius: 0px 0px 8px 8px;
198 198 -moz-border-radius: 0px 0px 8px 8px;
199 199 border-radius: 0px 0px 8px 8px;
200 200 height:37px;
201 201 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367
202 202 }
203 203
204 204 #header ul#logged-user li {
205 205 list-style:none;
206 206 float:left;
207 207 margin:8px 0 0;
208 208 padding:4px 12px;
209 209 border-left: 1px solid #316293;
210 210 }
211 211
212 212 #header ul#logged-user li.first {
213 213 border-left:none;
214 214 margin:4px;
215 215 }
216 216
217 217 #header ul#logged-user li.first div.gravatar {
218 218 margin-top:-2px;
219 219 }
220 220
221 221 #header ul#logged-user li.first div.account {
222 222 padding-top:4px;
223 223 float:left;
224 224 }
225 225
226 226 #header ul#logged-user li.last {
227 227 border-right:none;
228 228 }
229 229
230 230 #header ul#logged-user li a {
231 231 color:#fff;
232 232 font-weight:700;
233 233 text-decoration:none;
234 234 }
235 235
236 236 #header ul#logged-user li a:hover {
237 237 text-decoration:underline;
238 238 }
239 239
240 240 #header ul#logged-user li.highlight a {
241 241 color:#fff;
242 242 }
243 243
244 244 #header ul#logged-user li.highlight a:hover {
245 245 color:#FFF;
246 246 }
247 247
248 248 #header #header-inner {
249 249 height:40px;
250 250 clear:both;
251 251 position:relative;
252 252 background:#003367 url("../images/header_inner.png") repeat-x;
253 253 border-bottom:2px solid #fff;
254 254 margin:0;
255 255 padding:0;
256 256 }
257 257
258 258 #header #header-inner #home a {
259 259 height:40px;
260 260 width:46px;
261 261 display:block;
262 262 background:url("../images/button_home.png");
263 263 background-position:0 0;
264 264 margin:0;
265 265 padding:0;
266 266 }
267 267
268 268 #header #header-inner #home a:hover {
269 269 background-position:0 -40px;
270 270 }
271 271
272 272 #header #header-inner #logo h1 {
273 273 color:#FFF;
274 274 font-size:18px;
275 275 margin:10px 0 0 13px;
276 276 padding:0;
277 277 }
278 278
279 279 #header #header-inner #logo a {
280 280 color:#fff;
281 281 text-decoration:none;
282 282 }
283 283
284 284 #header #header-inner #logo a:hover {
285 285 color:#bfe3ff;
286 286 }
287 287
288 288 #header #header-inner #quick,#header #header-inner #quick ul {
289 289 position:relative;
290 290 float:right;
291 291 list-style-type:none;
292 292 list-style-position:outside;
293 293 margin:10px 5px 0 0;
294 294 padding:0;
295 295 }
296 296
297 297 #header #header-inner #quick li {
298 298 position:relative;
299 299 float:left;
300 300 margin:0 5px 0 0;
301 301 padding:0;
302 302 }
303 303
304 304 #header #header-inner #quick li a {
305 305 top:0;
306 306 left:0;
307 307 height:1%;
308 308 display:block;
309 309 clear:both;
310 310 overflow:hidden;
311 311 color:#FFF;
312 312 font-weight:700;
313 313 text-decoration:none;
314 314 background:#369 url("../images/quick_l.png") no-repeat top left;
315 315 padding:0;
316 316 }
317 317
318 318 #header #header-inner #quick li span.short {
319 319 padding:9px 6px 8px 6px;
320 320 }
321 321
322 322 #header #header-inner #quick li span {
323 323 top:0;
324 324 right:0;
325 325 height:1%;
326 326 display:block;
327 327 float:left;
328 328 background:url("../images/quick_r.png") no-repeat top right;
329 329 border-left:1px solid #3f6f9f;
330 330 margin:0;
331 331 padding:10px 12px 8px 10px;
332 332 }
333 333
334 334 #header #header-inner #quick li span.normal {
335 335 border:none;
336 336 padding:10px 12px 8px;
337 337 }
338 338
339 339 #header #header-inner #quick li span.icon {
340 340 top:0;
341 341 left:0;
342 342 border-left:none;
343 343 background:url("../images/quick_l.png") no-repeat top left;
344 344 border-right:1px solid #2e5c89;
345 345 padding:8px 8px 4px;
346 346 }
347 347
348 348 #header #header-inner #quick li span.icon_short {
349 349 top:0;
350 350 left:0;
351 351 border-left:none;
352 352 background:url("../images/quick_l.png") no-repeat top left;
353 353 border-right:1px solid #2e5c89;
354 354 padding:9px 4px 4px;
355 355 }
356 356
357 357 #header #header-inner #quick li a:hover {
358 358 background:#4e4e4e url("../images/quick_l_selected.png") no-repeat top left;
359 359 }
360 360
361 361 #header #header-inner #quick li a:hover span {
362 362 border-left:1px solid #545454;
363 363 background:url("../images/quick_r_selected.png") no-repeat top right;
364 364 }
365 365
366 366 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
367 367 border-left:none;
368 368 border-right:1px solid #464646;
369 369 background:url("../images/quick_l_selected.png") no-repeat top left;
370 370 }
371 371
372 372
373 373 #header #header-inner #quick ul {
374 374 top:29px;
375 375 right:0;
376 376 min-width:200px;
377 377 display:none;
378 378 position:absolute;
379 379 background:#FFF;
380 380 border:1px solid #666;
381 381 border-top:1px solid #003367;
382 382 z-index:100;
383 383 margin:0;
384 384 padding:0;
385 385 }
386 386
387 387 #header #header-inner #quick ul.repo_switcher {
388 388 max-height:275px;
389 389 overflow-x:hidden;
390 390 overflow-y:auto;
391 391 }
392 392 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
393 393 float:none;
394 394 margin:0;
395 395 border-bottom:2px solid #003367;
396 396 }
397 397
398 398
399 399 #header #header-inner #quick .repo_switcher_type{
400 400 position:absolute;
401 401 left:0;
402 402 top:9px;
403 403
404 404 }
405 405 #header #header-inner #quick li ul li {
406 406 border-bottom:1px solid #ddd;
407 407 }
408 408
409 409 #header #header-inner #quick li ul li a {
410 410 width:182px;
411 411 height:auto;
412 412 display:block;
413 413 float:left;
414 414 background:#FFF;
415 415 color:#003367;
416 416 font-weight:400;
417 417 margin:0;
418 418 padding:7px 9px;
419 419 }
420 420
421 421 #header #header-inner #quick li ul li a:hover {
422 422 color:#000;
423 423 background:#FFF;
424 424 }
425 425
426 426 #header #header-inner #quick ul ul {
427 427 top:auto;
428 428 }
429 429
430 430 #header #header-inner #quick li ul ul {
431 431 right:200px;
432 432 max-height:275px;
433 433 overflow:auto;
434 434 overflow-x:hidden;
435 435 white-space:normal;
436 436 }
437 437
438 438 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
439 439 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
440 440 width:167px;
441 441 margin:0;
442 442 padding:12px 9px 7px 24px;
443 443 }
444 444
445 445 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
446 446 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
447 447 min-width:167px;
448 448 margin:0;
449 449 padding:12px 9px 7px 24px;
450 450 }
451 451
452 452 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
453 453 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
454 454 min-width:167px;
455 455 margin:0;
456 456 padding:12px 9px 7px 24px;
457 457 }
458 458
459 459 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
460 460 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
461 461 min-width:167px;
462 462 margin:0 0 0 14px;
463 463 padding:12px 9px 7px 24px;
464 464 }
465 465
466 466 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
467 467 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
468 468 min-width:167px;
469 469 margin:0 0 0 14px;
470 470 padding:12px 9px 7px 24px;
471 471 }
472 472
473 473 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
474 474 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
475 475 width:167px;
476 476 margin:0;
477 477 padding:12px 9px 7px 24px;
478 478 }
479 479
480 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
481 background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
482 width:167px;
483 margin:0;
484 padding:12px 9px 7px 24px;
485 }
486
480 487 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
481 488 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
482 489 width:167px;
483 490 margin:0;
484 491 padding:12px 9px 7px 24px;
485 492 }
486 493
487 494 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
488 495 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
489 496 width:167px;
490 497 margin:0;
491 498 padding:12px 9px 7px 24px;
492 499 }
493 500
494 501 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
495 502 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
496 503 width:167px;
497 504 margin:0;
498 505 padding:12px 9px 7px 24px;
499 506 }
500 507
501 508 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
502 509 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
503 510 width:167px;
504 511 margin:0;
505 512 padding:12px 9px 7px 24px;
506 513 }
507 514
508 515 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
509 516 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
510 517 width:167px;
511 518 margin:0;
512 519 padding:12px 9px 7px 24px;
513 520 }
514 521
515 522 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
516 523 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
517 524 width:167px;
518 525 margin:0;
519 526 padding:12px 9px 7px 24px;
520 527 }
521 528
522 529 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
523 530 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
524 531 width:167px;
525 532 margin:0;
526 533 padding:12px 9px 7px 24px;
527 534 }
528 535
529 536 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
530 537 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
531 538 width:167px;
532 539 margin:0;
533 540 padding:12px 9px 7px 24px;
534 541 }
535 542
536 543 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
537 544 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
538 545 width:167px;
539 546 margin:0;
540 547 padding:12px 9px 7px 24px;
541 548 }
542 549
543 550 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
544 551 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
545 552 width:167px;
546 553 margin:0;
547 554 padding:12px 9px 7px 24px;
548 555 }
549 556
550 557 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
551 558 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
552 559 width:167px;
553 560 margin:0;
554 561 padding:12px 9px 7px 24px;
555 562 }
556 563
557 564 #content #left {
558 565 left:0;
559 566 width:280px;
560 567 position:absolute;
561 568 }
562 569
563 570 #content #right {
564 571 margin:0 60px 10px 290px;
565 572 }
566 573
567 574 #content div.box {
568 575 clear:both;
569 576 overflow:hidden;
570 577 background:#fff;
571 578 margin:0 0 10px;
572 579 padding:0 0 10px;
573 580 }
574 581
575 582 #content div.box-left {
576 583 width:49%;
577 584 clear:none;
578 585 float:left;
579 586 margin:0 0 10px;
580 587 }
581 588
582 589 #content div.box-right {
583 590 width:49%;
584 591 clear:none;
585 592 float:right;
586 593 margin:0 0 10px;
587 594 }
588 595
589 596 #content div.box div.title {
590 597 clear:both;
591 598 overflow:hidden;
592 599 background:#369 url("../images/header_inner.png") repeat-x;
593 600 margin:0 0 20px;
594 601 padding:0;
595 602 }
596 603
597 604 #content div.box div.title h5 {
598 605 float:left;
599 606 border:none;
600 607 color:#fff;
601 608 text-transform:uppercase;
602 609 margin:0;
603 610 padding:11px 0 11px 10px;
604 611 }
605 612
606 613 #content div.box div.title ul.links li {
607 614 list-style:none;
608 615 float:left;
609 616 margin:0;
610 617 padding:0;
611 618 }
612 619
613 620 #content div.box div.title ul.links li a {
614 621 border-left: 1px solid #316293;
615 622 color: #FFFFFF;
616 623 display: block;
617 624 float: left;
618 625 font-size: 13px;
619 626 font-weight: 700;
620 627 height: 1%;
621 628 margin: 0;
622 629 padding: 11px 22px 12px;
623 630 text-decoration: none;
624 631 }
625 632
626 633 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
627 634 clear:both;
628 635 overflow:hidden;
629 636 border-bottom:1px solid #DDD;
630 637 margin:10px 20px;
631 638 padding:0 0 15px;
632 639 }
633 640
634 641 #content div.box p {
635 642 color:#5f5f5f;
636 643 font-size:12px;
637 644 line-height:150%;
638 645 margin:0 24px 10px;
639 646 padding:0;
640 647 }
641 648
642 649 #content div.box blockquote {
643 650 border-left:4px solid #DDD;
644 651 color:#5f5f5f;
645 652 font-size:11px;
646 653 line-height:150%;
647 654 margin:0 34px;
648 655 padding:0 0 0 14px;
649 656 }
650 657
651 658 #content div.box blockquote p {
652 659 margin:10px 0;
653 660 padding:0;
654 661 }
655 662
656 663 #content div.box dl {
657 664 margin:10px 24px;
658 665 }
659 666
660 667 #content div.box dt {
661 668 font-size:12px;
662 669 margin:0;
663 670 }
664 671
665 672 #content div.box dd {
666 673 font-size:12px;
667 674 margin:0;
668 675 padding:8px 0 8px 15px;
669 676 }
670 677
671 678 #content div.box li {
672 679 font-size:12px;
673 680 padding:4px 0;
674 681 }
675 682
676 683 #content div.box ul.disc,#content div.box ul.circle {
677 684 margin:10px 24px 10px 38px;
678 685 }
679 686
680 687 #content div.box ul.square {
681 688 margin:10px 24px 10px 40px;
682 689 }
683 690
684 691 #content div.box img.left {
685 692 border:none;
686 693 float:left;
687 694 margin:10px 10px 10px 0;
688 695 }
689 696
690 697 #content div.box img.right {
691 698 border:none;
692 699 float:right;
693 700 margin:10px 0 10px 10px;
694 701 }
695 702
696 703 #content div.box div.messages {
697 704 clear:both;
698 705 overflow:hidden;
699 706 margin:0 20px;
700 707 padding:0;
701 708 }
702 709
703 710 #content div.box div.message {
704 711 clear:both;
705 712 overflow:hidden;
706 713 margin:0;
707 714 padding:10px 0;
708 715 }
709 716
710 717 #content div.box div.message a {
711 718 font-weight:400 !important;
712 719 }
713 720
714 721 #content div.box div.message div.image {
715 722 float:left;
716 723 margin:9px 0 0 5px;
717 724 padding:6px;
718 725 }
719 726
720 727 #content div.box div.message div.image img {
721 728 vertical-align:middle;
722 729 margin:0;
723 730 }
724 731
725 732 #content div.box div.message div.text {
726 733 float:left;
727 734 margin:0;
728 735 padding:9px 6px;
729 736 }
730 737
731 738 #content div.box div.message div.dismiss a {
732 739 height:16px;
733 740 width:16px;
734 741 display:block;
735 742 background:url("../images/icons/cross.png") no-repeat;
736 743 margin:15px 14px 0 0;
737 744 padding:0;
738 745 }
739 746
740 747 #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 {
741 748 border:none;
742 749 margin:0;
743 750 padding:0;
744 751 }
745 752
746 753 #content div.box div.message div.text span {
747 754 height:1%;
748 755 display:block;
749 756 margin:0;
750 757 padding:5px 0 0;
751 758 }
752 759
753 760 #content div.box div.message-error {
754 761 height:1%;
755 762 clear:both;
756 763 overflow:hidden;
757 764 background:#FBE3E4;
758 765 border:1px solid #FBC2C4;
759 766 color:#860006;
760 767 }
761 768
762 769 #content div.box div.message-error h6 {
763 770 color:#860006;
764 771 }
765 772
766 773 #content div.box div.message-warning {
767 774 height:1%;
768 775 clear:both;
769 776 overflow:hidden;
770 777 background:#FFF6BF;
771 778 border:1px solid #FFD324;
772 779 color:#5f5200;
773 780 }
774 781
775 782 #content div.box div.message-warning h6 {
776 783 color:#5f5200;
777 784 }
778 785
779 786 #content div.box div.message-notice {
780 787 height:1%;
781 788 clear:both;
782 789 overflow:hidden;
783 790 background:#8FBDE0;
784 791 border:1px solid #6BACDE;
785 792 color:#003863;
786 793 }
787 794
788 795 #content div.box div.message-notice h6 {
789 796 color:#003863;
790 797 }
791 798
792 799 #content div.box div.message-success {
793 800 height:1%;
794 801 clear:both;
795 802 overflow:hidden;
796 803 background:#E6EFC2;
797 804 border:1px solid #C6D880;
798 805 color:#4e6100;
799 806 }
800 807
801 808 #content div.box div.message-success h6 {
802 809 color:#4e6100;
803 810 }
804 811
805 812 #content div.box div.form div.fields div.field {
806 813 height:1%;
807 814 border-bottom:1px solid #DDD;
808 815 clear:both;
809 816 margin:0;
810 817 padding:10px 0;
811 818 }
812 819
813 820 #content div.box div.form div.fields div.field-first {
814 821 padding:0 0 10px;
815 822 }
816 823
817 824 #content div.box div.form div.fields div.field-noborder {
818 825 border-bottom:0 !important;
819 826 }
820 827
821 828 #content div.box div.form div.fields div.field span.error-message {
822 829 height:1%;
823 830 display:inline-block;
824 831 color:red;
825 832 margin:8px 0 0 4px;
826 833 padding:0;
827 834 }
828 835
829 836 #content div.box div.form div.fields div.field span.success {
830 837 height:1%;
831 838 display:block;
832 839 color:#316309;
833 840 margin:8px 0 0;
834 841 padding:0;
835 842 }
836 843
837 844 #content div.box div.form div.fields div.field div.label {
838 845 left:70px;
839 846 width:auto;
840 847 position:absolute;
841 848 margin:0;
842 849 padding:8px 0 0 5px;
843 850 }
844 851
845 852 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
846 853 clear:both;
847 854 overflow:hidden;
848 855 left:0;
849 856 width:auto;
850 857 position:relative;
851 858 margin:0;
852 859 padding:0 0 8px;
853 860 }
854 861
855 862 #content div.box div.form div.fields div.field div.label-select {
856 863 padding:5px 0 0 5px;
857 864 }
858 865
859 866 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
860 867 padding:0 0 8px;
861 868 }
862 869
863 870 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
864 871 padding:0 0 8px !important;
865 872 }
866 873
867 874 #content div.box div.form div.fields div.field div.label label, div.label label{
868 875 color:#393939;
869 876 font-weight:700;
870 877 }
871 878
872 879 #content div.box div.form div.fields div.field div.input {
873 880 margin:0 0 0 200px;
874 881 }
875 882 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
876 883 margin:0 0 0 0px;
877 884 }
878 885
879 886 #content div.box div.form div.fields div.field div.input input {
880 887 background:#FFF;
881 888 border-top:1px solid #b3b3b3;
882 889 border-left:1px solid #b3b3b3;
883 890 border-right:1px solid #eaeaea;
884 891 border-bottom:1px solid #eaeaea;
885 892 color:#000;
886 893 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
887 894 font-size:11px;
888 895 margin:0;
889 896 padding:7px 7px 6px;
890 897 }
891 898
892 899
893 900
894 901 #content div.box div.form div.fields div.field div.input input.small {
895 902 width:30%;
896 903 }
897 904
898 905 #content div.box div.form div.fields div.field div.input input.medium {
899 906 width:55%;
900 907 }
901 908
902 909 #content div.box div.form div.fields div.field div.input input.large {
903 910 width:85%;
904 911 }
905 912
906 913 #content div.box div.form div.fields div.field div.input input.date {
907 914 width:177px;
908 915 }
909 916
910 917 #content div.box div.form div.fields div.field div.input input.button {
911 918 background:#D4D0C8;
912 919 border-top:1px solid #FFF;
913 920 border-left:1px solid #FFF;
914 921 border-right:1px solid #404040;
915 922 border-bottom:1px solid #404040;
916 923 color:#000;
917 924 margin:0;
918 925 padding:4px 8px;
919 926 }
920 927
921 928 #content div.box div.form div.fields div.field div.textarea {
922 929 border-top:1px solid #b3b3b3;
923 930 border-left:1px solid #b3b3b3;
924 931 border-right:1px solid #eaeaea;
925 932 border-bottom:1px solid #eaeaea;
926 933 margin:0 0 0 200px;
927 934 padding:10px;
928 935 }
929 936
930 937 #content div.box div.form div.fields div.field div.textarea-editor {
931 938 border:1px solid #ddd;
932 939 padding:0;
933 940 }
934 941
935 942 #content div.box div.form div.fields div.field div.textarea textarea {
936 943 width:100%;
937 944 height:220px;
938 945 overflow:hidden;
939 946 background:#FFF;
940 947 color:#000;
941 948 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
942 949 font-size:11px;
943 950 outline:none;
944 951 border-width:0;
945 952 margin:0;
946 953 padding:0;
947 954 }
948 955
949 956 #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 {
950 957 width:100%;
951 958 height:100px;
952 959 }
953 960
954 961 #content div.box div.form div.fields div.field div.textarea table {
955 962 width:100%;
956 963 border:none;
957 964 margin:0;
958 965 padding:0;
959 966 }
960 967
961 968 #content div.box div.form div.fields div.field div.textarea table td {
962 969 background:#DDD;
963 970 border:none;
964 971 padding:0;
965 972 }
966 973
967 974 #content div.box div.form div.fields div.field div.textarea table td table {
968 975 width:auto;
969 976 border:none;
970 977 margin:0;
971 978 padding:0;
972 979 }
973 980
974 981 #content div.box div.form div.fields div.field div.textarea table td table td {
975 982 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
976 983 font-size:11px;
977 984 padding:5px 5px 5px 0;
978 985 }
979 986
980 987 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
981 988 background:#f6f6f6;
982 989 border-color:#666;
983 990 }
984 991
985 992 div.form div.fields div.field div.button {
986 993 margin:0;
987 994 padding:0 0 0 8px;
988 995 }
989 996
990 997 div.form div.fields div.field div.highlight .ui-button {
991 998 background:#4e85bb url("../images/button_highlight.png") repeat-x;
992 999 border-top:1px solid #5c91a4;
993 1000 border-left:1px solid #2a6f89;
994 1001 border-right:1px solid #2b7089;
995 1002 border-bottom:1px solid #1a6480;
996 1003 color:#FFF;
997 1004 margin:0;
998 1005 padding:6px 12px;
999 1006 }
1000 1007
1001 1008 div.form div.fields div.field div.highlight .ui-state-hover {
1002 1009 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1003 1010 border-top:1px solid #78acbf;
1004 1011 border-left:1px solid #34819e;
1005 1012 border-right:1px solid #35829f;
1006 1013 border-bottom:1px solid #257897;
1007 1014 color:#FFF;
1008 1015 margin:0;
1009 1016 padding:6px 12px;
1010 1017 }
1011 1018
1012 1019 #content div.box div.form div.fields div.buttons div.highlight input.ui-button {
1013 1020 background:#4e85bb url("../images/button_highlight.png") repeat-x;
1014 1021 border-top:1px solid #5c91a4;
1015 1022 border-left:1px solid #2a6f89;
1016 1023 border-right:1px solid #2b7089;
1017 1024 border-bottom:1px solid #1a6480;
1018 1025 color:#fff;
1019 1026 margin:0;
1020 1027 padding:6px 12px;
1021 1028 }
1022 1029
1023 1030 #content div.box div.form div.fields div.buttons div.highlight input.ui-state-hover {
1024 1031 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
1025 1032 border-top:1px solid #78acbf;
1026 1033 border-left:1px solid #34819e;
1027 1034 border-right:1px solid #35829f;
1028 1035 border-bottom:1px solid #257897;
1029 1036 color:#fff;
1030 1037 margin:0;
1031 1038 padding:6px 12px;
1032 1039 }
1033 1040
1034 1041 #content div.box table {
1035 1042 width:100%;
1036 1043 border-collapse:collapse;
1037 1044 margin:0;
1038 1045 padding:0;
1039 1046 }
1040 1047
1041 1048 #content div.box table th {
1042 1049 background:#eee;
1043 1050 border-bottom:1px solid #ddd;
1044 1051 padding:5px 0px 5px 5px;
1045 1052 }
1046 1053
1047 1054 #content div.box table th.left {
1048 1055 text-align:left;
1049 1056 }
1050 1057
1051 1058 #content div.box table th.right {
1052 1059 text-align:right;
1053 1060 }
1054 1061
1055 1062 #content div.box table th.center {
1056 1063 text-align:center;
1057 1064 }
1058 1065
1059 1066 #content div.box table th.selected {
1060 1067 vertical-align:middle;
1061 1068 padding:0;
1062 1069 }
1063 1070
1064 1071 #content div.box table td {
1065 1072 background:#fff;
1066 1073 border-bottom:1px solid #cdcdcd;
1067 1074 vertical-align:middle;
1068 1075 padding:5px;
1069 1076 }
1070 1077
1071 1078 #content div.box table tr.selected td {
1072 1079 background:#FFC;
1073 1080 }
1074 1081
1075 1082 #content div.box table td.selected {
1076 1083 width:3%;
1077 1084 text-align:center;
1078 1085 vertical-align:middle;
1079 1086 padding:0;
1080 1087 }
1081 1088
1082 1089 #content div.box table td.action {
1083 1090 width:45%;
1084 1091 text-align:left;
1085 1092 }
1086 1093
1087 1094 #content div.box table td.date {
1088 1095 width:33%;
1089 1096 text-align:center;
1090 1097 }
1091 1098
1092 1099 #content div.box div.action {
1093 1100 float:right;
1094 1101 background:#FFF;
1095 1102 text-align:right;
1096 1103 margin:10px 0 0;
1097 1104 padding:0;
1098 1105 }
1099 1106
1100 1107 #content div.box div.action select {
1101 1108 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1102 1109 font-size:11px;
1103 1110 margin:0;
1104 1111 }
1105 1112
1106 1113 #content div.box div.action .ui-selectmenu {
1107 1114 margin:0;
1108 1115 padding:0;
1109 1116 }
1110 1117
1111 1118 #content div.box div.pagination {
1112 1119 height:1%;
1113 1120 clear:both;
1114 1121 overflow:hidden;
1115 1122 margin:10px 0 0;
1116 1123 padding:0;
1117 1124 }
1118 1125
1119 1126 #content div.box div.pagination ul.pager {
1120 1127 float:right;
1121 1128 text-align:right;
1122 1129 margin:0;
1123 1130 padding:0;
1124 1131 }
1125 1132
1126 1133 #content div.box div.pagination ul.pager li {
1127 1134 height:1%;
1128 1135 float:left;
1129 1136 list-style:none;
1130 1137 background:#ebebeb url("../images/pager.png") repeat-x;
1131 1138 border-top:1px solid #dedede;
1132 1139 border-left:1px solid #cfcfcf;
1133 1140 border-right:1px solid #c4c4c4;
1134 1141 border-bottom:1px solid #c4c4c4;
1135 1142 color:#4A4A4A;
1136 1143 font-weight:700;
1137 1144 margin:0 0 0 4px;
1138 1145 padding:0;
1139 1146 }
1140 1147
1141 1148 #content div.box div.pagination ul.pager li.separator {
1142 1149 padding:6px;
1143 1150 }
1144 1151
1145 1152 #content div.box div.pagination ul.pager li.current {
1146 1153 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1147 1154 border-top:1px solid #ccc;
1148 1155 border-left:1px solid #bebebe;
1149 1156 border-right:1px solid #b1b1b1;
1150 1157 border-bottom:1px solid #afafaf;
1151 1158 color:#515151;
1152 1159 padding:6px;
1153 1160 }
1154 1161
1155 1162 #content div.box div.pagination ul.pager li a {
1156 1163 height:1%;
1157 1164 display:block;
1158 1165 float:left;
1159 1166 color:#515151;
1160 1167 text-decoration:none;
1161 1168 margin:0;
1162 1169 padding:6px;
1163 1170 }
1164 1171
1165 1172 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1166 1173 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1167 1174 border-top:1px solid #ccc;
1168 1175 border-left:1px solid #bebebe;
1169 1176 border-right:1px solid #b1b1b1;
1170 1177 border-bottom:1px solid #afafaf;
1171 1178 margin:-1px;
1172 1179 }
1173 1180
1174 1181 #content div.box div.pagination-wh {
1175 1182 height:1%;
1176 1183 clear:both;
1177 1184 overflow:hidden;
1178 1185 text-align:right;
1179 1186 margin:10px 0 0;
1180 1187 padding:0;
1181 1188 }
1182 1189
1183 1190 #content div.box div.pagination-right {
1184 1191 float:right;
1185 1192 }
1186 1193
1187 1194 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1188 1195 height:1%;
1189 1196 float:left;
1190 1197 background:#ebebeb url("../images/pager.png") repeat-x;
1191 1198 border-top:1px solid #dedede;
1192 1199 border-left:1px solid #cfcfcf;
1193 1200 border-right:1px solid #c4c4c4;
1194 1201 border-bottom:1px solid #c4c4c4;
1195 1202 color:#4A4A4A;
1196 1203 font-weight:700;
1197 1204 margin:0 0 0 4px;
1198 1205 padding:6px;
1199 1206 }
1200 1207
1201 1208 #content div.box div.pagination-wh span.pager_curpage {
1202 1209 height:1%;
1203 1210 float:left;
1204 1211 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1205 1212 border-top:1px solid #ccc;
1206 1213 border-left:1px solid #bebebe;
1207 1214 border-right:1px solid #b1b1b1;
1208 1215 border-bottom:1px solid #afafaf;
1209 1216 color:#515151;
1210 1217 font-weight:700;
1211 1218 margin:0 0 0 4px;
1212 1219 padding:6px;
1213 1220 }
1214 1221
1215 1222 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1216 1223 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1217 1224 border-top:1px solid #ccc;
1218 1225 border-left:1px solid #bebebe;
1219 1226 border-right:1px solid #b1b1b1;
1220 1227 border-bottom:1px solid #afafaf;
1221 1228 text-decoration:none;
1222 1229 }
1223 1230
1224 1231 #content div.box div.traffic div.legend {
1225 1232 clear:both;
1226 1233 overflow:hidden;
1227 1234 border-bottom:1px solid #ddd;
1228 1235 margin:0 0 10px;
1229 1236 padding:0 0 10px;
1230 1237 }
1231 1238
1232 1239 #content div.box div.traffic div.legend h6 {
1233 1240 float:left;
1234 1241 border:none;
1235 1242 margin:0;
1236 1243 padding:0;
1237 1244 }
1238 1245
1239 1246 #content div.box div.traffic div.legend li {
1240 1247 list-style:none;
1241 1248 float:left;
1242 1249 font-size:11px;
1243 1250 margin:0;
1244 1251 padding:0 8px 0 4px;
1245 1252 }
1246 1253
1247 1254 #content div.box div.traffic div.legend li.visits {
1248 1255 border-left:12px solid #edc240;
1249 1256 }
1250 1257
1251 1258 #content div.box div.traffic div.legend li.pageviews {
1252 1259 border-left:12px solid #afd8f8;
1253 1260 }
1254 1261
1255 1262 #content div.box div.traffic table {
1256 1263 width:auto;
1257 1264 }
1258 1265
1259 1266 #content div.box div.traffic table td {
1260 1267 background:transparent;
1261 1268 border:none;
1262 1269 padding:2px 3px 3px;
1263 1270 }
1264 1271
1265 1272 #content div.box div.traffic table td.legendLabel {
1266 1273 padding:0 3px 2px;
1267 1274 }
1268 1275
1269 1276 #summary{
1270 1277
1271 1278 }
1272 1279
1273 1280 #summary .desc{
1274 1281 white-space: pre;
1275 1282 width: 100%;
1276 1283 }
1277 1284
1278 1285 #summary .repo_name{
1279 1286 font-size: 1.6em;
1280 1287 font-weight: bold;
1281 1288 vertical-align: baseline;
1282 1289 clear:right
1283 1290 }
1284 1291
1285 1292
1286 1293 #footer {
1287 1294 clear:both;
1288 1295 overflow:hidden;
1289 1296 text-align:right;
1290 1297 margin:0;
1291 1298 padding:0 10px 4px;
1292 1299 margin:-10px 0 0;
1293 1300 }
1294 1301
1295 1302 #footer div#footer-inner {
1296 1303 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1297 1304 border-top:2px solid #FFFFFF;
1298 1305 }
1299 1306
1300 1307 #footer div#footer-inner p {
1301 1308 padding:15px 25px 15px 0;
1302 1309 color:#FFF;
1303 1310 font-weight:700;
1304 1311 }
1305 1312 #footer div#footer-inner .footer-link {
1306 1313 float:left;
1307 1314 padding-left:10px;
1308 1315 }
1309 1316 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1310 1317 color:#FFF;
1311 1318 }
1312 1319
1313 1320 #login div.title {
1314 1321 width:420px;
1315 1322 clear:both;
1316 1323 overflow:hidden;
1317 1324 position:relative;
1318 1325 background:#003367 url("../images/header_inner.png") repeat-x;
1319 1326 margin:0 auto;
1320 1327 padding:0;
1321 1328 }
1322 1329
1323 1330 #login div.inner {
1324 1331 width:380px;
1325 1332 background:#FFF url("../images/login.png") no-repeat top left;
1326 1333 border-top:none;
1327 1334 border-bottom:none;
1328 1335 margin:0 auto;
1329 1336 padding:20px;
1330 1337 }
1331 1338
1332 1339 #login div.form div.fields div.field div.label {
1333 1340 width:173px;
1334 1341 float:left;
1335 1342 text-align:right;
1336 1343 margin:2px 10px 0 0;
1337 1344 padding:5px 0 0 5px;
1338 1345 }
1339 1346
1340 1347 #login div.form div.fields div.field div.input input {
1341 1348 width:176px;
1342 1349 background:#FFF;
1343 1350 border-top:1px solid #b3b3b3;
1344 1351 border-left:1px solid #b3b3b3;
1345 1352 border-right:1px solid #eaeaea;
1346 1353 border-bottom:1px solid #eaeaea;
1347 1354 color:#000;
1348 1355 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1349 1356 font-size:11px;
1350 1357 margin:0;
1351 1358 padding:7px 7px 6px;
1352 1359 }
1353 1360
1354 1361 #login div.form div.fields div.buttons {
1355 1362 clear:both;
1356 1363 overflow:hidden;
1357 1364 border-top:1px solid #DDD;
1358 1365 text-align:right;
1359 1366 margin:0;
1360 1367 padding:10px 0 0;
1361 1368 }
1362 1369
1363 1370 #login div.form div.links {
1364 1371 clear:both;
1365 1372 overflow:hidden;
1366 1373 margin:10px 0 0;
1367 1374 padding:0 0 2px;
1368 1375 }
1369 1376
1370 1377 #quick_login{
1371 1378 top: 31px;
1372 1379 background-color: rgb(0, 51, 103);
1373 1380 z-index: 999;
1374 1381 height: 150px;
1375 1382 position: absolute;
1376 1383 margin-left: -16px;
1377 1384 width: 281px;
1378 1385 border-radius: 0 0 8px 8px;
1379 1386 }
1380 1387
1381 1388 #quick_login div.form div.fields{
1382 1389 padding-top: 2px;
1383 1390 padding-left:10px;
1384 1391 }
1385 1392
1386 1393 #quick_login div.form div.fields div.field{
1387 1394 padding: 5px;
1388 1395 }
1389 1396
1390 1397 #quick_login div.form div.fields div.field div.label label{
1391 1398 color:#fff;
1392 1399 padding-bottom: 3px;
1393 1400 }
1394 1401
1395 1402 #quick_login div.form div.fields div.field div.input input {
1396 1403 width:236px;
1397 1404 background:#FFF;
1398 1405 border-top:1px solid #b3b3b3;
1399 1406 border-left:1px solid #b3b3b3;
1400 1407 border-right:1px solid #eaeaea;
1401 1408 border-bottom:1px solid #eaeaea;
1402 1409 color:#000;
1403 1410 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1404 1411 font-size:11px;
1405 1412 margin:0;
1406 1413 padding:5px 7px 4px;
1407 1414 }
1408 1415
1409 1416 #quick_login div.form div.fields div.buttons {
1410 1417 clear:both;
1411 1418 overflow:hidden;
1412 1419 text-align:right;
1413 1420 margin:0;
1414 1421 padding:10px 14px 0;
1415 1422 }
1416 1423
1417 1424 #quick_login div.form div.fields div.buttons input.ui-button{
1418 1425 background:#e5e3e3 url("../images/button.png") repeat-x;
1419 1426 border-top:1px solid #DDD;
1420 1427 border-left:1px solid #c6c6c6;
1421 1428 border-right:1px solid #DDD;
1422 1429 border-bottom:1px solid #c6c6c6;
1423 1430 color:#515151;
1424 1431 margin:0;
1425 1432 padding:4px 10px;
1426 1433 }
1427 1434
1428 1435 #quick_login div.form div.links {
1429 1436 clear:both;
1430 1437 overflow:hidden;
1431 1438 margin:10px 0 0;
1432 1439 padding:0 0 2px;
1433 1440 }
1434 1441
1435 1442 #register div.title {
1436 1443 clear:both;
1437 1444 overflow:hidden;
1438 1445 position:relative;
1439 1446 background:#003367 url("../images/header_inner.png") repeat-x;
1440 1447 margin:0 auto;
1441 1448 padding:0;
1442 1449 }
1443 1450
1444 1451 #register div.inner {
1445 1452 background:#FFF;
1446 1453 border-top:none;
1447 1454 border-bottom:none;
1448 1455 margin:0 auto;
1449 1456 padding:20px;
1450 1457 }
1451 1458
1452 1459 #register div.form div.fields div.field div.label {
1453 1460 width:135px;
1454 1461 float:left;
1455 1462 text-align:right;
1456 1463 margin:2px 10px 0 0;
1457 1464 padding:5px 0 0 5px;
1458 1465 }
1459 1466
1460 1467 #register div.form div.fields div.field div.input input {
1461 1468 width:300px;
1462 1469 background:#FFF;
1463 1470 border-top:1px solid #b3b3b3;
1464 1471 border-left:1px solid #b3b3b3;
1465 1472 border-right:1px solid #eaeaea;
1466 1473 border-bottom:1px solid #eaeaea;
1467 1474 color:#000;
1468 1475 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1469 1476 font-size:11px;
1470 1477 margin:0;
1471 1478 padding:7px 7px 6px;
1472 1479 }
1473 1480
1474 1481 #register div.form div.fields div.buttons {
1475 1482 clear:both;
1476 1483 overflow:hidden;
1477 1484 border-top:1px solid #DDD;
1478 1485 text-align:left;
1479 1486 margin:0;
1480 1487 padding:10px 0 0 150px;
1481 1488 }
1482 1489
1483 1490 #register div.form div.fields div.buttons div.highlight input.ui-button {
1484 1491 background:url("../images/button_highlight.png") repeat-x scroll 0 0 #4E85BB;
1485 1492 color:#FFF;
1486 1493 border-color:#5C91A4 #2B7089 #1A6480 #2A6F89;
1487 1494 border-style:solid;
1488 1495 border-width:1px;
1489 1496 }
1490 1497
1491 1498 #register div.form div.activation_msg {
1492 1499 padding-top:4px;
1493 1500 padding-bottom:4px;
1494 1501 }
1495 1502
1496 1503 #journal .journal_day{
1497 1504 font-size:20px;
1498 1505 padding:10px 0px;
1499 1506 border-bottom:2px solid #DDD;
1500 1507 margin-left:10px;
1501 1508 margin-right:10px;
1502 1509 }
1503 1510
1504 1511 #journal .journal_container{
1505 1512 padding:5px;
1506 1513 clear:both;
1507 1514 margin:0px 5px 0px 10px;
1508 1515 }
1509 1516
1510 1517 #journal .journal_action_container{
1511 1518 padding-left:38px;
1512 1519 }
1513 1520
1514 1521 #journal .journal_user{
1515 1522 color: #747474;
1516 1523 font-size: 14px;
1517 1524 font-weight: bold;
1518 1525 height: 30px;
1519 1526 }
1520 1527 #journal .journal_icon{
1521 1528 clear: both;
1522 1529 float: left;
1523 1530 padding-right: 4px;
1524 1531 padding-top: 3px;
1525 1532 }
1526 1533 #journal .journal_action{
1527 1534 padding-top:4px;
1528 1535 min-height:2px;
1529 1536 float:left
1530 1537 }
1531 1538 #journal .journal_action_params{
1532 1539 clear: left;
1533 1540 padding-left: 22px;
1534 1541 }
1535 1542 #journal .journal_repo{
1536 1543 float: left;
1537 1544 margin-left: 6px;
1538 1545 padding-top: 3px;
1539 1546 }
1540 1547 #journal .date{
1541 1548 clear: both;
1542 1549 color: #777777;
1543 1550 font-size: 11px;
1544 1551 padding-left: 22px;
1545 1552 }
1546 1553 #journal .journal_repo .journal_repo_name{
1547 1554 font-weight: bold;
1548 1555 font-size: 1.1em;
1549 1556 }
1550 1557 #journal .compare_view{
1551 1558 padding: 5px 0px 5px 0px;
1552 1559 width: 95px;
1553 1560 }
1554 1561 .journal_highlight{
1555 1562 font-weight: bold;
1556 1563 padding: 0 2px;
1557 1564 vertical-align: bottom;
1558 1565 }
1559 1566 .trending_language_tbl,.trending_language_tbl td {
1560 1567 border:0 !important;
1561 1568 margin:0 !important;
1562 1569 padding:0 !important;
1563 1570 }
1564 1571
1565 1572 .trending_language {
1566 1573 background-color:#003367;
1567 1574 color:#FFF;
1568 1575 display:block;
1569 1576 min-width:20px;
1570 1577 text-decoration:none;
1571 1578 height:12px;
1572 1579 margin-bottom:4px;
1573 1580 margin-left:5px;
1574 1581 white-space:pre;
1575 1582 padding:3px;
1576 1583 }
1577 1584
1578 1585 h3.files_location {
1579 1586 font-size:1.8em;
1580 1587 font-weight:700;
1581 1588 border-bottom:none !important;
1582 1589 margin:10px 0 !important;
1583 1590 }
1584 1591
1585 1592 #files_data dl dt {
1586 1593 float:left;
1587 1594 width:115px;
1588 1595 margin:0 !important;
1589 1596 padding:5px;
1590 1597 }
1591 1598
1592 1599 #files_data dl dd {
1593 1600 margin:0 !important;
1594 1601 padding:5px !important;
1595 1602 }
1596 1603
1597 1604 #changeset_content {
1598 1605 border:1px solid #CCC;
1599 1606 padding:5px;
1600 1607 }
1601 1608 #changeset_compare_view_content{
1602 1609 border:1px solid #CCC;
1603 1610 padding:5px;
1604 1611 }
1605 1612
1606 1613 #changeset_content .container {
1607 1614 min-height:120px;
1608 1615 font-size:1.2em;
1609 1616 overflow:hidden;
1610 1617 }
1611 1618
1612 1619 #changeset_compare_view_content .compare_view_commits{
1613 1620 width: auto !important;
1614 1621 }
1615 1622
1616 1623 #changeset_compare_view_content .compare_view_commits td{
1617 1624 padding:0px 0px 0px 12px !important;
1618 1625 }
1619 1626
1620 1627 #changeset_content .container .right {
1621 1628 float:right;
1622 1629 width:25%;
1623 1630 text-align:right;
1624 1631 }
1625 1632
1626 1633 #changeset_content .container .left .message {
1627 1634 font-style:italic;
1628 1635 color:#556CB5;
1629 1636 white-space:pre-wrap;
1630 1637 }
1631 1638
1632 1639 .cs_files .cur_cs{
1633 1640 margin:10px 2px;
1634 1641 font-weight: bold;
1635 1642 }
1636 1643
1637 1644 .cs_files .node{
1638 1645 float: left;
1639 1646 }
1640 1647 .cs_files .changes{
1641 1648 float: right;
1642 1649 }
1643 1650 .cs_files .changes .added{
1644 1651 background-color: #BBFFBB;
1645 1652 float: left;
1646 1653 text-align: center;
1647 1654 font-size: 90%;
1648 1655 }
1649 1656 .cs_files .changes .deleted{
1650 1657 background-color: #FF8888;
1651 1658 float: left;
1652 1659 text-align: center;
1653 1660 font-size: 90%;
1654 1661 }
1655 1662 .cs_files .cs_added {
1656 1663 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1657 1664 height:16px;
1658 1665 padding-left:20px;
1659 1666 margin-top:7px;
1660 1667 text-align:left;
1661 1668 }
1662 1669
1663 1670 .cs_files .cs_changed {
1664 1671 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1665 1672 height:16px;
1666 1673 padding-left:20px;
1667 1674 margin-top:7px;
1668 1675 text-align:left;
1669 1676 }
1670 1677
1671 1678 .cs_files .cs_removed {
1672 1679 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1673 1680 height:16px;
1674 1681 padding-left:20px;
1675 1682 margin-top:7px;
1676 1683 text-align:left;
1677 1684 }
1678 1685
1679 1686 #graph {
1680 1687 overflow:hidden;
1681 1688 }
1682 1689
1683 1690 #graph_nodes {
1684 1691 width:160px;
1685 1692 float:left;
1686 1693 margin-left:-50px;
1687 1694 margin-top:5px;
1688 1695 }
1689 1696
1690 1697 #graph_content {
1691 1698 width:800px;
1692 1699 float:left;
1693 1700 }
1694 1701
1695 1702 #graph_content .container_header {
1696 1703 border:1px solid #CCC;
1697 1704 padding:10px;
1698 1705 }
1699 1706 #graph_content #rev_range_container{
1700 1707 padding:10px 0px;
1701 1708 }
1702 1709 #graph_content .container {
1703 1710 border-bottom:1px solid #CCC;
1704 1711 border-left:1px solid #CCC;
1705 1712 border-right:1px solid #CCC;
1706 1713 min-height:80px;
1707 1714 overflow:hidden;
1708 1715 font-size:1.2em;
1709 1716 }
1710 1717
1711 1718 #graph_content .container .right {
1712 1719 float:right;
1713 1720 width:28%;
1714 1721 text-align:right;
1715 1722 padding-bottom:5px;
1716 1723 }
1717 1724
1718 1725 #graph_content .container .left .date {
1719 1726 font-weight:700;
1720 1727 padding-bottom:5px;
1721 1728 }
1722 1729 #graph_content .container .left .date span{
1723 1730 vertical-align: text-top;
1724 1731 }
1725 1732
1726 1733 #graph_content .container .left .message {
1727 1734 font-size:100%;
1728 1735 padding-top:3px;
1729 1736 white-space:pre-wrap;
1730 1737 }
1731 1738
1732 1739 .right div {
1733 1740 clear:both;
1734 1741 }
1735 1742
1736 1743 .right .changes .added,.changed,.removed {
1737 1744 border:1px solid #DDD;
1738 1745 display:block;
1739 1746 float:right;
1740 1747 text-align:center;
1741 1748 min-width:15px;
1742 1749 cursor: help;
1743 1750 }
1744 1751 .right .changes .large {
1745 1752 border:1px solid #DDD;
1746 1753 display:block;
1747 1754 float:right;
1748 1755 text-align:center;
1749 1756 min-width:45px;
1750 1757 cursor: help;
1751 1758 background: #54A9F7;
1752 1759 }
1753 1760
1754 1761 .right .changes .added {
1755 1762 background:#BFB;
1756 1763 }
1757 1764
1758 1765 .right .changes .changed {
1759 1766 background:#FD8;
1760 1767 }
1761 1768
1762 1769 .right .changes .removed {
1763 1770 background:#F88;
1764 1771 }
1765 1772
1766 1773 .right .merge {
1767 1774 vertical-align:top;
1768 1775 font-size:0.75em;
1769 1776 font-weight:700;
1770 1777 }
1771 1778
1772 1779 .right .parent {
1773 1780 font-size:90%;
1774 1781 font-family:monospace;
1775 1782 }
1776 1783
1777 1784 .right .logtags .branchtag {
1778 1785 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1779 1786 display:block;
1780 1787 font-size:0.8em;
1781 1788 padding:11px 16px 0 0;
1782 1789 }
1783 1790
1784 1791 .right .logtags .tagtag {
1785 1792 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1786 1793 display:block;
1787 1794 font-size:0.8em;
1788 1795 padding:11px 16px 0 0;
1789 1796 }
1790 1797
1791 1798 div.browserblock {
1792 1799 overflow:hidden;
1793 1800 border:1px solid #ccc;
1794 1801 background:#f8f8f8;
1795 1802 font-size:100%;
1796 1803 line-height:125%;
1797 1804 padding:0;
1798 1805 }
1799 1806
1800 1807 div.browserblock .browser-header {
1801 1808 background:#FFF;
1802 1809 padding:10px 0px 25px 0px;
1803 1810 width: 100%;
1804 1811 }
1805 1812 div.browserblock .browser-nav {
1806 1813 float:left
1807 1814 }
1808 1815
1809 1816 div.browserblock .browser-branch {
1810 1817 float:left;
1811 1818 }
1812 1819
1813 1820 div.browserblock .browser-branch label {
1814 1821 color:#4A4A4A;
1815 1822 vertical-align:text-top;
1816 1823 }
1817 1824
1818 1825 div.browserblock .browser-header span {
1819 1826 margin-left:5px;
1820 1827 font-weight:700;
1821 1828 }
1822 1829
1823 1830 div.browserblock .browser-body {
1824 1831 background:#EEE;
1825 1832 border-top:1px solid #CCC;
1826 1833 }
1827 1834
1828 1835 table.code-browser {
1829 1836 border-collapse:collapse;
1830 1837 width:100%;
1831 1838 }
1832 1839
1833 1840 table.code-browser tr {
1834 1841 margin:3px;
1835 1842 }
1836 1843
1837 1844 table.code-browser thead th {
1838 1845 background-color:#EEE;
1839 1846 height:20px;
1840 1847 font-size:1.1em;
1841 1848 font-weight:700;
1842 1849 text-align:left;
1843 1850 padding-left:10px;
1844 1851 }
1845 1852
1846 1853 table.code-browser tbody td {
1847 1854 padding-left:10px;
1848 1855 height:20px;
1849 1856 }
1850 1857
1851 1858 table.code-browser .browser-file {
1852 1859 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1853 1860 height:16px;
1854 1861 padding-left:20px;
1855 1862 text-align:left;
1856 1863 }
1857 1864 .diffblock .changeset_file{
1858 1865 background:url("../images/icons/file.png") no-repeat scroll 3px;
1859 1866 height:16px;
1860 1867 padding-left:22px;
1861 1868 text-align:left;
1862 1869 font-size: 14px;
1863 1870 }
1864 1871
1865 1872 .diffblock .changeset_header{
1866 1873 margin-left: 6px !important;
1867 1874 }
1868 1875
1869 1876 table.code-browser .browser-dir {
1870 1877 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1871 1878 height:16px;
1872 1879 padding-left:20px;
1873 1880 text-align:left;
1874 1881 }
1875 1882
1876 1883 .box .search {
1877 1884 clear:both;
1878 1885 overflow:hidden;
1879 1886 margin:0;
1880 1887 padding:0 20px 10px;
1881 1888 }
1882 1889
1883 1890 .box .search div.search_path {
1884 1891 background:none repeat scroll 0 0 #EEE;
1885 1892 border:1px solid #CCC;
1886 1893 color:blue;
1887 1894 margin-bottom:10px;
1888 1895 padding:10px 0;
1889 1896 }
1890 1897
1891 1898 .box .search div.search_path div.link {
1892 1899 font-weight:700;
1893 1900 margin-left:25px;
1894 1901 }
1895 1902
1896 1903 .box .search div.search_path div.link a {
1897 1904 color:#003367;
1898 1905 cursor:pointer;
1899 1906 text-decoration:none;
1900 1907 }
1901 1908
1902 1909 #path_unlock {
1903 1910 color:red;
1904 1911 font-size:1.2em;
1905 1912 padding-left:4px;
1906 1913 }
1907 1914
1908 1915 .info_box span {
1909 1916 margin-left:3px;
1910 1917 margin-right:3px;
1911 1918 }
1912 1919
1913 1920 .info_box .rev {
1914 1921 color: #003367;
1915 1922 font-size: 1.6em;
1916 1923 font-weight: bold;
1917 1924 vertical-align: sub;
1918 1925 }
1919 1926
1920 1927
1921 1928 .info_box input#at_rev,.info_box input#size {
1922 1929 background:#FFF;
1923 1930 border-top:1px solid #b3b3b3;
1924 1931 border-left:1px solid #b3b3b3;
1925 1932 border-right:1px solid #eaeaea;
1926 1933 border-bottom:1px solid #eaeaea;
1927 1934 color:#000;
1928 1935 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1929 1936 font-size:12px;
1930 1937 margin:0;
1931 1938 padding:1px 5px 1px;
1932 1939 }
1933 1940
1934 1941
1935 1942
1936 1943 .info_box input#view {
1937 1944 text-align:center;
1938 1945 padding:4px 3px 2px 2px;
1939 1946 }
1940 1947
1941 1948 .yui-overlay,.yui-panel-container {
1942 1949 visibility:hidden;
1943 1950 position:absolute;
1944 1951 z-index:2;
1945 1952 }
1946 1953
1947 1954 .yui-tt {
1948 1955 visibility:hidden;
1949 1956 position:absolute;
1950 1957 color:#666;
1951 1958 background-color:#FFF;
1952 1959 font-family:arial, helvetica, verdana, sans-serif;
1953 1960 border:2px solid #003367;
1954 1961 font:100% sans-serif;
1955 1962 width:auto;
1956 1963 opacity:1px;
1957 1964 padding:8px;
1958 1965 white-space: pre;
1959 1966 -webkit-border-radius: 8px 8px 8px 8px;
1960 1967 -khtml-border-radius: 8px 8px 8px 8px;
1961 1968 -moz-border-radius: 8px 8px 8px 8px;
1962 1969 border-radius: 8px 8px 8px 8px;
1963 1970 }
1964 1971
1965 1972 .ac {
1966 1973 vertical-align:top;
1967 1974 }
1968 1975
1969 1976 .ac .yui-ac {
1970 1977 position:relative;
1971 1978 font-family:arial;
1972 1979 font-size:100%;
1973 1980 }
1974 1981
1975 1982 .ac .perm_ac {
1976 1983 width:15em;
1977 1984 }
1978 1985
1979 1986 .ac .yui-ac-input {
1980 1987 width:100%;
1981 1988 }
1982 1989
1983 1990 .ac .yui-ac-container {
1984 1991 position:absolute;
1985 1992 top:1.6em;
1986 1993 width:100%;
1987 1994 }
1988 1995
1989 1996 .ac .yui-ac-content {
1990 1997 position:absolute;
1991 1998 width:100%;
1992 1999 border:1px solid gray;
1993 2000 background:#fff;
1994 2001 overflow:hidden;
1995 2002 z-index:9050;
1996 2003 }
1997 2004
1998 2005 .ac .yui-ac-shadow {
1999 2006 position:absolute;
2000 2007 width:100%;
2001 2008 background:#000;
2002 2009 -moz-opacity:0.1px;
2003 2010 opacity:.10;
2004 2011 filter:alpha(opacity = 10);
2005 2012 z-index:9049;
2006 2013 margin:.3em;
2007 2014 }
2008 2015
2009 2016 .ac .yui-ac-content ul {
2010 2017 width:100%;
2011 2018 margin:0;
2012 2019 padding:0;
2013 2020 }
2014 2021
2015 2022 .ac .yui-ac-content li {
2016 2023 cursor:default;
2017 2024 white-space:nowrap;
2018 2025 margin:0;
2019 2026 padding:2px 5px;
2020 2027 }
2021 2028
2022 2029 .ac .yui-ac-content li.yui-ac-prehighlight {
2023 2030 background:#B3D4FF;
2024 2031 }
2025 2032
2026 2033 .ac .yui-ac-content li.yui-ac-highlight {
2027 2034 background:#556CB5;
2028 2035 color:#FFF;
2029 2036 }
2030 2037
2031 2038
2032 2039 .follow{
2033 2040 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2034 2041 height: 16px;
2035 2042 width: 20px;
2036 2043 cursor: pointer;
2037 2044 display: block;
2038 2045 float: right;
2039 2046 margin-top: 2px;
2040 2047 }
2041 2048
2042 2049 .following{
2043 2050 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2044 2051 height: 16px;
2045 2052 width: 20px;
2046 2053 cursor: pointer;
2047 2054 display: block;
2048 2055 float: right;
2049 2056 margin-top: 2px;
2050 2057 }
2051 2058
2052 2059 .currently_following{
2053 2060 padding-left: 10px;
2054 2061 padding-bottom:5px;
2055 2062 }
2056 2063
2057 2064 .add_icon {
2058 2065 background:url("../images/icons/add.png") no-repeat scroll 3px;
2059 2066 padding-left:20px;
2060 2067 padding-top:0px;
2061 2068 text-align:left;
2062 2069 }
2063 2070
2064 2071 .edit_icon {
2065 2072 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2066 2073 padding-left:20px;
2067 2074 padding-top:0px;
2068 2075 text-align:left;
2069 2076 }
2070 2077
2071 2078 .delete_icon {
2072 2079 background:url("../images/icons/delete.png") no-repeat scroll 3px;
2073 2080 padding-left:20px;
2074 2081 padding-top:0px;
2075 2082 text-align:left;
2076 2083 }
2077 2084
2078 2085 .refresh_icon {
2079 2086 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
2080 2087 padding-left:20px;
2081 2088 padding-top:0px;
2082 2089 text-align:left;
2083 2090 }
2084 2091
2085 2092 .pull_icon {
2086 2093 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2087 2094 padding-left:20px;
2088 2095 padding-top:0px;
2089 2096 text-align:left;
2090 2097 }
2091 2098
2092 2099 .rss_icon {
2093 2100 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2094 2101 padding-left:20px;
2095 2102 padding-top:0px;
2096 2103 text-align:left;
2097 2104 }
2098 2105
2099 2106 .atom_icon {
2100 2107 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2101 2108 padding-left:20px;
2102 2109 padding-top:0px;
2103 2110 text-align:left;
2104 2111 }
2105 2112
2106 2113 .archive_icon {
2107 2114 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2108 2115 padding-left:20px;
2109 2116 text-align:left;
2110 2117 padding-top:1px;
2111 2118 }
2112 2119
2113 2120 .start_following_icon {
2114 2121 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2115 2122 padding-left:20px;
2116 2123 text-align:left;
2117 2124 padding-top:0px;
2118 2125 }
2119 2126
2120 2127 .stop_following_icon {
2121 2128 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2122 2129 padding-left:20px;
2123 2130 text-align:left;
2124 2131 padding-top:0px;
2125 2132 }
2126 2133
2127 2134 .action_button {
2128 2135 border:0;
2129 2136 display:inline;
2130 2137 }
2131 2138
2132 2139 .action_button:hover {
2133 2140 border:0;
2134 2141 text-decoration:underline;
2135 2142 cursor:pointer;
2136 2143 }
2137 2144
2138 2145 #switch_repos {
2139 2146 position:absolute;
2140 2147 height:25px;
2141 2148 z-index:1;
2142 2149 }
2143 2150
2144 2151 #switch_repos select {
2145 2152 min-width:150px;
2146 2153 max-height:250px;
2147 2154 z-index:1;
2148 2155 }
2149 2156
2150 2157 .breadcrumbs {
2151 2158 border:medium none;
2152 2159 color:#FFF;
2153 2160 float:left;
2154 2161 text-transform:uppercase;
2155 2162 font-weight:700;
2156 2163 font-size:14px;
2157 2164 margin:0;
2158 2165 padding:11px 0 11px 10px;
2159 2166 }
2160 2167
2161 2168 .breadcrumbs a {
2162 2169 color:#FFF;
2163 2170 }
2164 2171
2165 2172 .flash_msg ul {
2166 2173 margin:0;
2167 2174 padding:0 0 10px;
2168 2175 }
2169 2176
2170 2177 .error_msg {
2171 2178 background-color:#FFCFCF;
2172 2179 background-image:url("../images/icons/error_msg.png");
2173 2180 border:1px solid #FF9595;
2174 2181 color:#C30;
2175 2182 }
2176 2183
2177 2184 .warning_msg {
2178 2185 background-color:#FFFBCC;
2179 2186 background-image:url("../images/icons/warning_msg.png");
2180 2187 border:1px solid #FFF35E;
2181 2188 color:#C69E00;
2182 2189 }
2183 2190
2184 2191 .success_msg {
2185 2192 background-color:#D5FFCF;
2186 2193 background-image:url("../images/icons/success_msg.png");
2187 2194 border:1px solid #97FF88;
2188 2195 color:#090;
2189 2196 }
2190 2197
2191 2198 .notice_msg {
2192 2199 background-color:#DCE3FF;
2193 2200 background-image:url("../images/icons/notice_msg.png");
2194 2201 border:1px solid #93A8FF;
2195 2202 color:#556CB5;
2196 2203 }
2197 2204
2198 2205 .success_msg,.error_msg,.notice_msg,.warning_msg {
2199 2206 background-position:10px center;
2200 2207 background-repeat:no-repeat;
2201 2208 font-size:12px;
2202 2209 font-weight:700;
2203 2210 min-height:14px;
2204 2211 line-height:14px;
2205 2212 margin-bottom:0;
2206 2213 margin-top:0;
2207 2214 display:block;
2208 2215 overflow:auto;
2209 2216 padding:6px 10px 6px 40px;
2210 2217 }
2211 2218
2212 2219 #msg_close {
2213 2220 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2214 2221 cursor:pointer;
2215 2222 height:16px;
2216 2223 position:absolute;
2217 2224 right:5px;
2218 2225 top:5px;
2219 2226 width:16px;
2220 2227 }
2221 2228
2222 2229 div#legend_container table,div#legend_choices table {
2223 2230 width:auto !important;
2224 2231 }
2225 2232
2226 2233 table#permissions_manage {
2227 2234 width:0 !important;
2228 2235 }
2229 2236
2230 2237 table#permissions_manage span.private_repo_msg {
2231 2238 font-size:0.8em;
2232 2239 opacity:0.6px;
2233 2240 }
2234 2241
2235 2242 table#permissions_manage td.private_repo_msg {
2236 2243 font-size:0.8em;
2237 2244 }
2238 2245
2239 2246 table#permissions_manage tr#add_perm_input td {
2240 2247 vertical-align:middle;
2241 2248 }
2242 2249
2243 2250 div.gravatar {
2244 2251 background-color:#FFF;
2245 2252 border:1px solid #D0D0D0;
2246 2253 float:left;
2247 2254 margin-right:0.7em;
2248 2255 padding:2px 2px 0;
2249 2256 }
2250 2257
2251 2258 #header,#content,#footer {
2252 2259 min-width:978px;
2253 2260 }
2254 2261
2255 2262 #content {
2256 2263 min-height:100%;
2257 2264 clear:both;
2258 2265 overflow:hidden;
2259 2266 padding:14px 10px;
2260 2267 }
2261 2268
2262 2269 #content div.box div.title div.search {
2263 2270 background:url("../images/title_link.png") no-repeat top left;
2264 2271 border-left:1px solid #316293;
2265 2272 }
2266 2273
2267 2274 #content div.box div.title div.search div.input input {
2268 2275 border:1px solid #316293;
2269 2276 }
2270 2277
2271 2278 #content div.box div.title div.search div.button input.ui-button {
2272 2279 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2273 2280 border:1px solid #316293;
2274 2281 border-left:none;
2275 2282 color:#FFF;
2276 2283 }
2277 2284
2278 2285 #content div.box input.ui-button-small {
2279 2286 background:#e5e3e3 url("../images/button.png") repeat-x;
2280 2287 border-top:1px solid #DDD;
2281 2288 border-left:1px solid #c6c6c6;
2282 2289 border-right:1px solid #DDD;
2283 2290 border-bottom:1px solid #c6c6c6;
2284 2291 color:#515151;
2285 2292 outline:none;
2286 2293 margin:0;
2287 2294 }
2288 2295
2289 2296 #content div.box input.ui-button-small-blue {
2290 2297 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2291 2298 border-top:1px solid #5c91a4;
2292 2299 border-left:1px solid #2a6f89;
2293 2300 border-right:1px solid #2b7089;
2294 2301 border-bottom:1px solid #1a6480;
2295 2302 color:#fff;
2296 2303 }
2297 2304
2298 2305 #content div.box input.ui-button-small submit,button{
2299 2306 cursor: pointer;
2300 2307 }
2301 2308
2302 2309 #content div.box div.title div.search div.button input.ui-state-hover {
2303 2310 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2304 2311 border:1px solid #316293;
2305 2312 border-left:none;
2306 2313 color:#FFF;
2307 2314 }
2308 2315
2309 2316 #content div.box div.form div.fields div.field div.highlight .ui-button {
2310 2317 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2311 2318 border-top:1px solid #5c91a4;
2312 2319 border-left:1px solid #2a6f89;
2313 2320 border-right:1px solid #2b7089;
2314 2321 border-bottom:1px solid #1a6480;
2315 2322 color:#fff;
2316 2323 }
2317 2324
2318 2325 #content div.box div.form div.fields div.field div.highlight .ui-state-hover {
2319 2326 background:#46a0c1 url("../images/button_highlight_selected.png") repeat-x;
2320 2327 border-top:1px solid #78acbf;
2321 2328 border-left:1px solid #34819e;
2322 2329 border-right:1px solid #35829f;
2323 2330 border-bottom:1px solid #257897;
2324 2331 color:#fff;
2325 2332 }
2326 2333
2327 2334 ins,div.options a:hover {
2328 2335 text-decoration:none;
2329 2336 }
2330 2337
2331 2338 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2332 2339 border:none;
2333 2340 }
2334 2341
2335 2342 img.icon,.right .merge img {
2336 2343 vertical-align:bottom;
2337 2344 }
2338 2345
2339 2346 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2340 2347 float:right;
2341 2348 margin:0;
2342 2349 padding:0;
2343 2350 }
2344 2351
2345 2352 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2346 2353 float:left;
2347 2354 }
2348 2355
2349 2356 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2350 2357 display:none;
2351 2358 }
2352 2359
2353 2360 #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 {
2354 2361 display:block;
2355 2362 }
2356 2363
2357 2364 #content div.graph{
2358 2365 padding:0 10px 10px;
2359 2366 }
2360 2367
2361 2368 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2362 2369 color:#bfe3ff;
2363 2370 }
2364 2371
2365 2372 #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 {
2366 2373 margin:10px 24px 10px 44px;
2367 2374 }
2368 2375
2369 2376 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
2370 2377 clear:both;
2371 2378 overflow:hidden;
2372 2379 margin:0;
2373 2380 padding:0 20px 10px;
2374 2381 }
2375 2382
2376 2383 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2377 2384 clear:both;
2378 2385 overflow:hidden;
2379 2386 margin:0;
2380 2387 padding:0;
2381 2388 }
2382 2389
2383 2390 #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 {
2384 2391 height:1%;
2385 2392 display:block;
2386 2393 color:#363636;
2387 2394 margin:0;
2388 2395 padding:2px 0 0;
2389 2396 }
2390 2397
2391 2398 #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 {
2392 2399 background:#FBE3E4;
2393 2400 border-top:1px solid #e1b2b3;
2394 2401 border-left:1px solid #e1b2b3;
2395 2402 border-right:1px solid #FBC2C4;
2396 2403 border-bottom:1px solid #FBC2C4;
2397 2404 }
2398 2405
2399 2406 #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 {
2400 2407 background:#E6EFC2;
2401 2408 border-top:1px solid #cebb98;
2402 2409 border-left:1px solid #cebb98;
2403 2410 border-right:1px solid #c6d880;
2404 2411 border-bottom:1px solid #c6d880;
2405 2412 }
2406 2413
2407 2414 #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 {
2408 2415 margin:0;
2409 2416 }
2410 2417
2411 2418 #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{
2412 2419 margin:0 0 0 0px !important;
2413 2420 padding:0;
2414 2421 }
2415 2422
2416 2423 #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 {
2417 2424 margin:0 0 0 200px;
2418 2425 padding:0;
2419 2426 }
2420 2427
2421 2428
2422 2429 #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 {
2423 2430 color:#000;
2424 2431 text-decoration:none;
2425 2432 }
2426 2433
2427 2434 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2428 2435 border:1px solid #666;
2429 2436 }
2430 2437
2431 2438 #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 {
2432 2439 clear:both;
2433 2440 overflow:hidden;
2434 2441 margin:0;
2435 2442 padding:8px 0 2px;
2436 2443 }
2437 2444
2438 2445 #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 {
2439 2446 float:left;
2440 2447 margin:0;
2441 2448 }
2442 2449
2443 2450 #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 {
2444 2451 height:1%;
2445 2452 display:block;
2446 2453 float:left;
2447 2454 margin:2px 0 0 4px;
2448 2455 }
2449 2456
2450 2457 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2451 2458 color:#000;
2452 2459 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2453 2460 font-size:11px;
2454 2461 font-weight:700;
2455 2462 margin:0;
2456 2463 }
2457 2464
2458 2465 div.form div.fields div.field div.button .ui-button,#content div.box div.form div.fields div.buttons input.ui-button {
2459 2466 background:#e5e3e3 url("../images/button.png") repeat-x;
2460 2467 border-top:1px solid #DDD;
2461 2468 border-left:1px solid #c6c6c6;
2462 2469 border-right:1px solid #DDD;
2463 2470 border-bottom:1px solid #c6c6c6;
2464 2471 color:#515151;
2465 2472 outline:none;
2466 2473 margin:0;
2467 2474 padding:6px 12px;
2468 2475 }
2469 2476
2470 2477 div.form div.fields div.field div.button .ui-state-hover,#content div.box div.form div.fields div.buttons input.ui-state-hover {
2471 2478 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2472 2479 border-top:1px solid #ccc;
2473 2480 border-left:1px solid #bebebe;
2474 2481 border-right:1px solid #b1b1b1;
2475 2482 border-bottom:1px solid #afafaf;
2476 2483 color:#515151;
2477 2484 outline:none;
2478 2485 margin:0;
2479 2486 padding:6px 12px;
2480 2487 }
2481 2488
2482 2489 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2483 2490 display:inline;
2484 2491 }
2485 2492
2486 2493 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2487 2494 margin:10px 0 0 200px;
2488 2495 padding:0;
2489 2496 }
2490 2497
2491 2498 #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 {
2492 2499 margin:10px 0 0;
2493 2500 }
2494 2501
2495 2502 #content div.box table td.user,#content div.box table td.address {
2496 2503 width:10%;
2497 2504 text-align:center;
2498 2505 }
2499 2506
2500 2507 #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 {
2501 2508 text-align:right;
2502 2509 margin:6px 0 0;
2503 2510 padding:0;
2504 2511 }
2505 2512
2506 2513 #content div.box div.action div.button input.ui-button,#login div.form div.fields div.buttons input.ui-button,#register div.form div.fields div.buttons input.ui-button {
2507 2514 background:#e5e3e3 url("../images/button.png") repeat-x;
2508 2515 border-top:1px solid #DDD;
2509 2516 border-left:1px solid #c6c6c6;
2510 2517 border-right:1px solid #DDD;
2511 2518 border-bottom:1px solid #c6c6c6;
2512 2519 color:#515151;
2513 2520 margin:0;
2514 2521 padding:6px 12px;
2515 2522 }
2516 2523
2517 2524 #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 {
2518 2525 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2519 2526 border-top:1px solid #ccc;
2520 2527 border-left:1px solid #bebebe;
2521 2528 border-right:1px solid #b1b1b1;
2522 2529 border-bottom:1px solid #afafaf;
2523 2530 color:#515151;
2524 2531 margin:0;
2525 2532 padding:6px 12px;
2526 2533 }
2527 2534
2528 2535 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2529 2536 text-align:left;
2530 2537 float:left;
2531 2538 margin:0;
2532 2539 padding:0;
2533 2540 }
2534 2541
2535 2542 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2536 2543 height:1%;
2537 2544 display:block;
2538 2545 float:left;
2539 2546 background:#ebebeb url("../images/pager.png") repeat-x;
2540 2547 border-top:1px solid #dedede;
2541 2548 border-left:1px solid #cfcfcf;
2542 2549 border-right:1px solid #c4c4c4;
2543 2550 border-bottom:1px solid #c4c4c4;
2544 2551 color:#4A4A4A;
2545 2552 font-weight:700;
2546 2553 margin:0;
2547 2554 padding:6px 8px;
2548 2555 }
2549 2556
2550 2557 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2551 2558 color:#B4B4B4;
2552 2559 padding:6px;
2553 2560 }
2554 2561
2555 2562 #login,#register {
2556 2563 width:520px;
2557 2564 margin:10% auto 0;
2558 2565 padding:0;
2559 2566 }
2560 2567
2561 2568 #login div.color,#register div.color {
2562 2569 clear:both;
2563 2570 overflow:hidden;
2564 2571 background:#FFF;
2565 2572 margin:10px auto 0;
2566 2573 padding:3px 3px 3px 0;
2567 2574 }
2568 2575
2569 2576 #login div.color a,#register div.color a {
2570 2577 width:20px;
2571 2578 height:20px;
2572 2579 display:block;
2573 2580 float:left;
2574 2581 margin:0 0 0 3px;
2575 2582 padding:0;
2576 2583 }
2577 2584
2578 2585 #login div.title h5,#register div.title h5 {
2579 2586 color:#fff;
2580 2587 margin:10px;
2581 2588 padding:0;
2582 2589 }
2583 2590
2584 2591 #login div.form div.fields div.field,#register div.form div.fields div.field {
2585 2592 clear:both;
2586 2593 overflow:hidden;
2587 2594 margin:0;
2588 2595 padding:0 0 10px;
2589 2596 }
2590 2597
2591 2598 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2592 2599 height:1%;
2593 2600 display:block;
2594 2601 color:red;
2595 2602 margin:8px 0 0;
2596 2603 padding:0;
2597 2604 max-width: 320px;
2598 2605 }
2599 2606
2600 2607 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2601 2608 color:#000;
2602 2609 font-weight:700;
2603 2610 }
2604 2611
2605 2612 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2606 2613 float:left;
2607 2614 margin:0;
2608 2615 padding:0;
2609 2616 }
2610 2617
2611 2618 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2612 2619 margin:0 0 0 184px;
2613 2620 padding:0;
2614 2621 }
2615 2622
2616 2623 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2617 2624 color:#565656;
2618 2625 font-weight:700;
2619 2626 }
2620 2627
2621 2628 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2622 2629 color:#000;
2623 2630 font-size:1em;
2624 2631 font-weight:700;
2625 2632 font-family:Verdana, Helvetica, Sans-Serif;
2626 2633 margin:0;
2627 2634 }
2628 2635
2629 2636 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2630 2637 width:600px;
2631 2638 }
2632 2639
2633 2640 #changeset_content .container .left,#graph_content .container .left {
2634 2641 float:left;
2635 2642 width:70%;
2636 2643 padding-left:5px;
2637 2644 }
2638 2645
2639 2646 #changeset_content .container .left .date,.ac .match {
2640 2647 font-weight:700;
2641 2648 padding-top: 5px;
2642 2649 padding-bottom:5px;
2643 2650 }
2644 2651
2645 2652 div#legend_container table td,div#legend_choices table td {
2646 2653 border:none !important;
2647 2654 height:20px !important;
2648 2655 padding:0 !important;
2649 2656 }
2650 2657
2651 2658 #q_filter{
2652 2659 border:0 none;
2653 2660 color:#AAAAAA;
2654 2661 margin-bottom:-4px;
2655 2662 margin-top:-4px;
2656 2663 padding-left:3px;
2657 2664 }
2658 2665
@@ -1,61 +1,60 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 ${h.form(url('repos'))}
4 4 <div class="form">
5 5 <!-- fields -->
6 6 <div class="fields">
7 7 <div class="field">
8 8 <div class="label">
9 9 <label for="repo_name">${_('Name')}:</label>
10 10 </div>
11 11 <div class="input">
12 12 ${h.text('repo_name',c.new_repo,class_="small")}
13 13 </div>
14 14 </div>
15 15 <div class="field">
16 16 <div class="label">
17 17 <label for="clone_uri">${_('Clone from')}:</label>
18 18 </div>
19 19 <div class="input">
20 20 ${h.text('clone_uri',class_="small")}
21 21 </div>
22 22 </div>
23 23 <div class="field">
24 24 <div class="label">
25 25 <label for="repo_group">${_('Repository group')}:</label>
26 26 </div>
27 27 <div class="input">
28 28 ${h.select('repo_group','',c.repo_groups,class_="medium")}
29 <span>${h.link_to(_('add new group'),h.url(''))}</span>
30 29 </div>
31 30 </div>
32 31 <div class="field">
33 32 <div class="label">
34 33 <label for="repo_type">${_('Type')}:</label>
35 34 </div>
36 35 <div class="input">
37 36 ${h.select('repo_type','hg',c.backends,class_="small")}
38 37 </div>
39 38 </div>
40 39 <div class="field">
41 40 <div class="label label-textarea">
42 41 <label for="description">${_('Description')}:</label>
43 42 </div>
44 43 <div class="textarea text-area editor">
45 44 ${h.textarea('description',cols=23,rows=5)}
46 45 </div>
47 46 </div>
48 47 <div class="field">
49 48 <div class="label label-checkbox">
50 49 <label for="private">${_('Private')}:</label>
51 50 </div>
52 51 <div class="checkboxes">
53 52 ${h.checkbox('private',value="True")}
54 53 </div>
55 54 </div>
56 55 <div class="buttons">
57 56 ${h.submit('add','add',class_="ui-button")}
58 57 </div>
59 58 </div>
60 59 </div>
61 60 ${h.end_form()}
@@ -1,383 +1,382 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit repository')} ${c.repo_info.repo_name} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('Repositories'),h.url('repos'))}
12 12 &raquo;
13 13 ${_('edit')} &raquo; ${h.link_to(c.repo_info.just_name,h.url('summary_home',repo_name=c.repo_name))}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 27 <div class="form">
28 28 <!-- fields -->
29 29 <div class="fields">
30 30 <div class="field">
31 31 <div class="label">
32 32 <label for="repo_name">${_('Name')}:</label>
33 33 </div>
34 34 <div class="input">
35 35 ${h.text('repo_name',class_="medium")}
36 36 </div>
37 37 </div>
38 38 <div class="field">
39 39 <div class="label">
40 40 <label for="clone_uri">${_('Clone uri')}:</label>
41 41 </div>
42 42 <div class="input">
43 43 ${h.text('clone_uri',class_="medium")}
44 44 </div>
45 45 </div>
46 46 <div class="field">
47 47 <div class="label">
48 48 <label for="repo_group">${_('Repository group')}:</label>
49 49 </div>
50 50 <div class="input">
51 51 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 <span>${h.link_to(_('add new group'),h.url(''))}</span>
53 52 </div>
54 53 </div>
55 54 <div class="field">
56 55 <div class="label">
57 56 <label for="repo_type">${_('Type')}:</label>
58 57 </div>
59 58 <div class="input">
60 59 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 60 </div>
62 61 </div>
63 62 <div class="field">
64 63 <div class="label label-textarea">
65 64 <label for="description">${_('Description')}:</label>
66 65 </div>
67 66 <div class="textarea text-area editor">
68 67 ${h.textarea('description',cols=23,rows=5)}
69 68 </div>
70 69 </div>
71 70
72 71 <div class="field">
73 72 <div class="label label-checkbox">
74 73 <label for="private">${_('Private')}:</label>
75 74 </div>
76 75 <div class="checkboxes">
77 76 ${h.checkbox('private',value="True")}
78 77 </div>
79 78 </div>
80 79 <div class="field">
81 80 <div class="label label-checkbox">
82 81 <label for="enable_statistics">${_('Enable statistics')}:</label>
83 82 </div>
84 83 <div class="checkboxes">
85 84 ${h.checkbox('enable_statistics',value="True")}
86 85 </div>
87 86 </div>
88 87 <div class="field">
89 88 <div class="label label-checkbox">
90 89 <label for="enable_downloads">${_('Enable downloads')}:</label>
91 90 </div>
92 91 <div class="checkboxes">
93 92 ${h.checkbox('enable_downloads',value="True")}
94 93 </div>
95 94 </div>
96 95 <div class="field">
97 96 <div class="label">
98 97 <label for="user">${_('Owner')}:</label>
99 98 </div>
100 99 <div class="input input-small ac">
101 100 <div class="perm_ac">
102 101 ${h.text('user',class_='yui-ac-input')}
103 102 <div id="owner_container"></div>
104 103 </div>
105 104 </div>
106 105 </div>
107 106
108 107 <div class="field">
109 108 <div class="label">
110 109 <label for="input">${_('Permissions')}:</label>
111 110 </div>
112 111 <div class="input">
113 112 <%include file="repo_edit_perms.html"/>
114 113 </div>
115 114
116 115 <div class="buttons">
117 116 ${h.submit('save','Save',class_="ui-button")}
118 117 ${h.reset('reset','Reset',class_="ui-button")}
119 118 </div>
120 119 </div>
121 120 </div>
122 121 </div>
123 122 ${h.end_form()}
124 123 <script type="text/javascript">
125 124 YAHOO.util.Event.onDOMReady(function(){
126 125 var D = YAHOO.util.Dom;
127 126 if(!D.hasClass('perm_new_member_name','error')){
128 127 D.setStyle('add_perm_input','display','none');
129 128 }
130 129 YAHOO.util.Event.addListener('add_perm','click',function(){
131 130 D.setStyle('add_perm_input','display','');
132 131 D.setStyle('add_perm','opacity','0.6');
133 132 D.setStyle('add_perm','cursor','default');
134 133 });
135 134 });
136 135 </script>
137 136 <script type="text/javascript">
138 137 YAHOO.example.FnMultipleFields = function(){
139 138 var myUsers = ${c.users_array|n};
140 139 var myGroups = ${c.users_groups_array|n};
141 140
142 141 // Define a custom search function for the DataSource of users
143 142 var matchUsers = function(sQuery) {
144 143 // Case insensitive matching
145 144 var query = sQuery.toLowerCase();
146 145 var i=0;
147 146 var l=myUsers.length;
148 147 var matches = [];
149 148
150 149 // Match against each name of each contact
151 150 for(; i<l; i++) {
152 151 contact = myUsers[i];
153 152 if((contact.fname.toLowerCase().indexOf(query) > -1) ||
154 153 (contact.lname.toLowerCase().indexOf(query) > -1) ||
155 154 (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) {
156 155 matches[matches.length] = contact;
157 156 }
158 157 }
159 158 return matches;
160 159 };
161 160
162 161 // Define a custom search function for the DataSource of usersGroups
163 162 var matchGroups = function(sQuery) {
164 163 // Case insensitive matching
165 164 var query = sQuery.toLowerCase();
166 165 var i=0;
167 166 var l=myGroups.length;
168 167 var matches = [];
169 168
170 169 // Match against each name of each contact
171 170 for(; i<l; i++) {
172 171 matched_group = myGroups[i];
173 172 if(matched_group.grname.toLowerCase().indexOf(query) > -1) {
174 173 matches[matches.length] = matched_group;
175 174 }
176 175 }
177 176 return matches;
178 177 };
179 178
180 179 //match all
181 180 var matchAll = function(sQuery){
182 181 u = matchUsers(sQuery);
183 182 g = matchGroups(sQuery);
184 183 return u.concat(g);
185 184 };
186 185
187 186 // DataScheme for members
188 187 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
189 188 memberDS.responseSchema = {
190 189 fields: ["id", "fname", "lname", "nname", "grname", "grmembers"]
191 190 };
192 191
193 192 // DataScheme for owner
194 193 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
195 194 ownerDS.responseSchema = {
196 195 fields: ["id", "fname", "lname", "nname"]
197 196 };
198 197
199 198 // Instantiate AutoComplete for perms
200 199 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
201 200 membersAC.useShadow = false;
202 201 membersAC.resultTypeList = false;
203 202
204 203 // Instantiate AutoComplete for owner
205 204 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
206 205 ownerAC.useShadow = false;
207 206 ownerAC.resultTypeList = false;
208 207
209 208
210 209 // Helper highlight function for the formatter
211 210 var highlightMatch = function(full, snippet, matchindex) {
212 211 return full.substring(0, matchindex) +
213 212 "<span class='match'>" +
214 213 full.substr(matchindex, snippet.length) +
215 214 "</span>" +
216 215 full.substring(matchindex + snippet.length);
217 216 };
218 217
219 218 // Custom formatter to highlight the matching letters
220 219 var custom_formatter = function(oResultData, sQuery, sResultMatch) {
221 220 var query = sQuery.toLowerCase();
222 221
223 222 if (oResultData.grname != undefined){
224 223 var grname = oResultData.grname;
225 224 var grmembers = oResultData.grmembers;
226 225 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
227 226 var grprefix = "${_('Group')}: ";
228 227 var grsuffix = " ("+grmembers+" ${_('members')})";
229 228
230 229 if (grnameMatchIndex > -1){
231 230 return grprefix+highlightMatch(grname,query,grnameMatchIndex)+grsuffix;
232 231 }
233 232
234 233 return grprefix+oResultData.grname+grsuffix;
235 234 }
236 235 else if(oResultData.fname != undefined){
237 236
238 237 var fname = oResultData.fname,
239 238 lname = oResultData.lname,
240 239 nname = oResultData.nname || "", // Guard against null value
241 240 fnameMatchIndex = fname.toLowerCase().indexOf(query),
242 241 lnameMatchIndex = lname.toLowerCase().indexOf(query),
243 242 nnameMatchIndex = nname.toLowerCase().indexOf(query),
244 243 displayfname, displaylname, displaynname;
245 244
246 245 if(fnameMatchIndex > -1) {
247 246 displayfname = highlightMatch(fname, query, fnameMatchIndex);
248 247 }
249 248 else {
250 249 displayfname = fname;
251 250 }
252 251
253 252 if(lnameMatchIndex > -1) {
254 253 displaylname = highlightMatch(lname, query, lnameMatchIndex);
255 254 }
256 255 else {
257 256 displaylname = lname;
258 257 }
259 258
260 259 if(nnameMatchIndex > -1) {
261 260 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
262 261 }
263 262 else {
264 263 displaynname = nname ? "(" + nname + ")" : "";
265 264 }
266 265
267 266 return displayfname + " " + displaylname + " " + displaynname;
268 267 }
269 268 else{
270 269 return '';
271 270 }
272 271 };
273 272 membersAC.formatResult = custom_formatter;
274 273 ownerAC.formatResult = custom_formatter;
275 274
276 275 var myHandler = function(sType, aArgs) {
277 276
278 277 var myAC = aArgs[0]; // reference back to the AC instance
279 278 var elLI = aArgs[1]; // reference to the selected LI element
280 279 var oData = aArgs[2]; // object literal of selected item's result data
281 280
282 281 //fill the autocomplete with value
283 282 if(oData.nname != undefined){
284 283 //users
285 284 myAC.getInputEl().value = oData.nname;
286 285 YUD.get('perm_new_member_type').value = 'user';
287 286 }
288 287 else{
289 288 //groups
290 289 myAC.getInputEl().value = oData.grname;
291 290 YUD.get('perm_new_member_type').value = 'users_group';
292 291 }
293 292
294 293 };
295 294
296 295 membersAC.itemSelectEvent.subscribe(myHandler);
297 296 ownerAC.itemSelectEvent.subscribe(myHandler);
298 297
299 298 return {
300 299 memberDS: memberDS,
301 300 ownerDS: ownerDS,
302 301 membersAC: membersAC,
303 302 ownerAC: ownerAC,
304 303 };
305 304 }();
306 305
307 306 </script>
308 307
309 308 </div>
310 309
311 310 <div class="box box-right">
312 311 <div class="title">
313 312 <h5>${_('Administration')}</h5>
314 313 </div>
315 314
316 315 <h3>${_('Statistics')}</h3>
317 316 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
318 317 <div class="form">
319 318 <div class="fields">
320 319 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="refresh_icon action_button",onclick="return confirm('Confirm to remove current statistics');")}
321 320 <div class="field" style="border:none">
322 321 <ul>
323 322 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
324 323 <li>${_('Percentage of stats gathered')}: ${c.stats_percentage} %</li>
325 324 </ul>
326 325 </div>
327 326
328 327 </div>
329 328 </div>
330 329 ${h.end_form()}
331 330
332 331 %if c.repo_info.clone_uri:
333 332 <h3>${_('Remote')}</h3>
334 333 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
335 334 <div class="form">
336 335 <div class="fields">
337 336 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="pull_icon action_button",onclick="return confirm('Confirm to pull changes from remote side');")}
338 337 <div class="field" style="border:none">
339 338 <ul>
340 339 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
341 340 </ul>
342 341 </div>
343 342 </div>
344 343 </div>
345 344 ${h.end_form()}
346 345 %endif
347 346
348 347 <h3>${_('Cache')}</h3>
349 348 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
350 349 <div class="form">
351 350 <div class="fields">
352 351 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="refresh_icon action_button",onclick="return confirm('Confirm to invalidate repository cache');")}
353 352 </div>
354 353 </div>
355 354 ${h.end_form()}
356 355
357 356 <h3>${_('Public journal')}</h3>
358 357 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
359 358 <div class="form">
360 359 <div class="fields">
361 360 ${h.hidden('auth_token',str(h.get_token()))}
362 361 %if c.in_public_journal:
363 362 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="stop_following_icon action_button")}
364 363 %else:
365 364 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="start_following_icon action_button")}
366 365 %endif
367 366 </div>
368 367 </div>
369 368 ${h.end_form()}
370 369
371 370 <h3>${_('Delete')}</h3>
372 371 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
373 372 <div class="form">
374 373 <div class="fields">
375 374 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
376 375 </div>
377 376 </div>
378 377 ${h.end_form()}
379 378
380 379 </div>
381 380
382 381
383 382 </%def> No newline at end of file
@@ -1,400 +1,401 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <!-- HEADER -->
5 5 <div id="header">
6 6 <!-- user -->
7 7 <ul id="logged-user">
8 8 <li class="first">
9 9
10 10 <div id="quick_login" style="display:none">
11 11 ${h.form(h.url('login_home',came_from=h.url.current()))}
12 12 <div class="form">
13 13 <div class="fields">
14 14 <div class="field">
15 15 <div class="label">
16 16 <label for="username">${_('Username')}:</label>
17 17 </div>
18 18 <div class="input">
19 19 ${h.text('username',class_='focus',size=40)}
20 20 </div>
21 21
22 22 </div>
23 23 <div class="field">
24 24 <div class="label">
25 25 <label for="password">${_('Password')}:</label>
26 26 </div>
27 27 <div class="input">
28 28 ${h.password('password',class_='focus',size=40)}
29 29 </div>
30 30
31 31 </div>
32 32 <div class="buttons">
33 33 ${h.submit('sign_in','Sign In',class_="ui-button")}
34 34 </div>
35 35 </div>
36 36 </div>
37 37 ${h.end_form()}
38 38 <script type="text/javascript">
39 39 YUE.on('quick_login_link','click',function(e){
40 40
41 41 if(YUD.hasClass('quick_login_link','enabled')){
42 42 YUD.setStyle('quick_login','display','none');
43 43 YUD.removeClass('quick_login_link','enabled');
44 44 }
45 45 else{
46 46 YUD.setStyle('quick_login','display','');
47 47 YUD.addClass('quick_login_link','enabled');
48 48 YUD.get('username').focus();
49 49 }
50 50 //make sure we don't redirect
51 51 YUE.preventDefault(e);
52 52 });
53 53
54 54 </script>
55 55 </div>
56 56
57 57 <div class="gravatar">
58 58 <img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,20)}" />
59 59 </div>
60 60 <div class="account">
61 61 %if c.rhodecode_user.username == 'default':
62 62 <a href="${h.url('public_journal')}">${_('Public journal')}</a>
63 63 %else:
64 64 ${h.link_to(c.rhodecode_user.username,h.url('admin_settings_my_account'),title='%s %s'%(c.rhodecode_user.name,c.rhodecode_user.lastname))}
65 65 %endif
66 66 </div>
67 67 </li>
68 68 <li>
69 69 <a href="${h.url('home')}">${_('Home')}</a>
70 70 </li>
71 71 %if c.rhodecode_user.username != 'default':
72 72 <li>
73 73 <a href="${h.url('journal')}">${_('Journal')}</a>
74 74 ##(${c.unread_journal}
75 75 </li>
76 76 %endif
77 77 %if c.rhodecode_user.username == 'default':
78 78 <li class="last highlight">${h.link_to(u'Login',h.url('login_home'),id='quick_login_link')}</li>
79 79 %else:
80 80 <li class="last highlight">${h.link_to(u'Log Out',h.url('logout_home'))}</li>
81 81 %endif
82 82 </ul>
83 83 <!-- end user -->
84 84 <div id="header-inner" class="title top-left-rounded-corner top-right-rounded-corner">
85 85 <div id="logo">
86 86 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
87 87 </div>
88 88 <!-- MENU -->
89 89 ${self.page_nav()}
90 90 <!-- END MENU -->
91 91 ${self.body()}
92 92 </div>
93 93 </div>
94 94 <!-- END HEADER -->
95 95
96 96 <!-- CONTENT -->
97 97 <div id="content">
98 98 <div class="flash_msg">
99 99 <% messages = h.flash.pop_messages() %>
100 100 % if messages:
101 101 <ul id="flash-messages">
102 102 % for message in messages:
103 103 <li class="${message.category}_msg">${message}</li>
104 104 % endfor
105 105 </ul>
106 106 % endif
107 107 </div>
108 108 <div id="main">
109 109 ${next.main()}
110 110 </div>
111 111 </div>
112 112 <!-- END CONTENT -->
113 113
114 114 <!-- FOOTER -->
115 115 <div id="footer">
116 116 <div id="footer-inner" class="title bottom-left-rounded-corner bottom-right-rounded-corner">
117 117 <div>
118 118 <p class="footer-link">
119 119 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
120 120 </p>
121 121 <p class="footer-link-right">
122 122 <a href="${h.url('rhodecode_official')}">RhodeCode</a>
123 123 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
124 124 </p>
125 125 </div>
126 126 </div>
127 127 <script type="text/javascript">
128 128 function tooltip_activate(){
129 129 ${h.tooltip.activate()}
130 130 }
131 131 tooltip_activate();
132 132 </script>
133 133 </div>
134 134 <!-- END FOOTER -->
135 135
136 136 ### MAKO DEFS ###
137 137 <%def name="page_nav()">
138 138 ${self.menu()}
139 139 </%def>
140 140
141 141 <%def name="breadcrumbs()">
142 142 <div class="breadcrumbs">
143 143 ${self.breadcrumbs_links()}
144 144 </div>
145 145 </%def>
146 146
147 147
148 148 <%def name="menu(current=None)">
149 149 <%
150 150 def is_current(selected):
151 151 if selected == current:
152 152 return h.literal('class="current"')
153 153 %>
154 154 %if current not in ['home','admin']:
155 155 ##REGULAR MENU
156 156 <ul id="quick">
157 157 <!-- repo switcher -->
158 158 <li>
159 159 <a id="repo_switcher" title="${_('Switch repository')}" href="#">
160 160 <span class="icon">
161 161 <img src="${h.url('/images/icons/database.png')}" alt="${_('Products')}" />
162 162 </span>
163 163 <span>&darr;</span>
164 164 </a>
165 165 <ul id="repo_switcher_list" class="repo_switcher">
166 166 <li>
167 167 <a href="#">${_('loading...')}</a>
168 168 </li>
169 169 </ul>
170 170 <script type="text/javascript">
171 171 YUE.on('repo_switcher','mouseover',function(){
172 172 function qfilter(){
173 173 var S = YAHOO.util.Selector;
174 174
175 175 var q_filter = YUD.get('q_filter_rs');
176 176 var F = YAHOO.namespace('q_filter_rs');
177 177
178 178 YUE.on(q_filter,'click',function(){
179 179 q_filter.value = '';
180 180 });
181 181
182 182 F.filterTimeout = null;
183 183
184 184 F.updateFilter = function() {
185 185 // Reset timeout
186 186 F.filterTimeout = null;
187 187
188 188 var obsolete = [];
189 189 var nodes = S.query('ul#repo_switcher_list li a.repo_name');
190 190 var req = YUD.get('q_filter_rs').value;
191 191 for (n in nodes){
192 192 YUD.setStyle(nodes[n].parentNode,'display','')
193 193 }
194 194 if (req){
195 195 for (n in nodes){
196 196 console.log(n);
197 197 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
198 198 obsolete.push(nodes[n]);
199 199 }
200 200 }
201 201 if(obsolete){
202 202 for (n in obsolete){
203 203 YUD.setStyle(obsolete[n].parentNode,'display','none');
204 204 }
205 205 }
206 206 }
207 207 }
208 208
209 209 YUE.on(q_filter,'keyup',function(e){
210 210 clearTimeout(F.filterTimeout);
211 211 setTimeout(F.updateFilter,600);
212 212 });
213 213 }
214 214 var loaded = YUD.hasClass('repo_switcher','loaded');
215 215 if(!loaded){
216 216 YUD.addClass('repo_switcher','loaded');
217 217 YAHOO.util.Connect.asyncRequest('GET',"${h.url('repo_switcher')}",{
218 218 success:function(o){
219 219 YUD.get('repo_switcher_list').innerHTML = o.responseText;
220 220 qfilter();
221 221 },
222 222 failure:function(o){
223 223 YUD.removeClass('repo_switcher','loaded');
224 224 }
225 225 },null);
226 226 }
227 227 return false;
228 228 });
229 229 </script>
230 230 </li>
231 231
232 232 <li ${is_current('summary')}>
233 233 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=c.repo_name)}">
234 234 <span class="icon">
235 235 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
236 236 </span>
237 237 <span>${_('Summary')}</span>
238 238 </a>
239 239 </li>
240 240 ##<li ${is_current('shortlog')}>
241 241 ## <a title="${_('Shortlog')}" href="${h.url('shortlog_home',repo_name=c.repo_name)}">
242 242 ## <span class="icon">
243 243 ## <img src="${h.url("/images/icons/application_view_list.png")}" alt="${_('Shortlog')}" />
244 244 ## </span>
245 245 ## <span>${_('Shortlog')}</span>
246 246 ## </a>
247 247 ##</li>
248 248 <li ${is_current('changelog')}>
249 249 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=c.repo_name)}">
250 250 <span class="icon">
251 251 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
252 252 </span>
253 253 <span>${_('Changelog')}</span>
254 254 </a>
255 255 </li>
256 256
257 257 <li ${is_current('switch_to')}>
258 258 <a title="${_('Switch to')}" href="#">
259 259 <span class="icon">
260 260 <img src="${h.url('/images/icons/arrow_switch.png')}" alt="${_('Switch to')}" />
261 261 </span>
262 262 <span>${_('Switch to')}</span>
263 263 </a>
264 264 <ul>
265 265 <li>
266 266 ${h.link_to('%s (%s)' % (_('branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
267 267 <ul>
268 268 %if c.rhodecode_repo.branches.values():
269 269 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
270 270 <li>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}</li>
271 271 %endfor
272 272 %else:
273 273 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
274 274 %endif
275 275 </ul>
276 276 </li>
277 277 <li>
278 278 ${h.link_to('%s (%s)' % (_('tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
279 279 <ul>
280 280 %if c.rhodecode_repo.tags.values():
281 281 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
282 282 <li>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}</li>
283 283 %endfor
284 284 %else:
285 285 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
286 286 %endif
287 287 </ul>
288 288 </li>
289 289 </ul>
290 290 </li>
291 291 <li ${is_current('files')}>
292 292 <a title="${_('Files')}" href="${h.url('files_home',repo_name=c.repo_name)}">
293 293 <span class="icon">
294 294 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
295 295 </span>
296 296 <span>${_('Files')}</span>
297 297 </a>
298 298 </li>
299 299
300 300 <li ${is_current('options')}>
301 301 <a title="${_('Options')}" href="#">
302 302 <span class="icon">
303 303 <img src="${h.url('/images/icons/table_gear.png')}" alt="${_('Admin')}" />
304 304 </span>
305 305 <span>${_('Options')}</span>
306 306 </a>
307 307 <ul>
308 308 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
309 309 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
310 310 <li>${h.link_to(_('settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
311 311 %else:
312 312 <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
313 313 %endif
314 314 %endif
315 315 <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li>
316 316 <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li>
317 317
318 318 % if h.HasPermissionAll('hg.admin')('access admin main page'):
319 319 <li>
320 320 ${h.link_to(_('admin'),h.url('admin_home'),class_='admin')}
321 321 <%def name="admin_menu()">
322 322 <ul>
323 323 <li>${h.link_to(_('journal'),h.url('admin_home'),class_='journal')}</li>
324 324 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
325 <li>${h.link_to(_('repositories groups'),h.url('repos_groups'),class_='repos_groups')}</li>
325 326 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
326 327 <li>${h.link_to(_('users groups'),h.url('users_groups'),class_='groups')}</li>
327 328 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
328 329 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
329 330 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
330 331 </ul>
331 332 </%def>
332 333
333 334 ${admin_menu()}
334 335 </li>
335 336 % endif
336 337 </ul>
337 338 </li>
338 339
339 340 <li>
340 341 <a title="${_('Followers')}" href="${h.url('repo_followers_home',repo_name=c.repo_name)}">
341 342 <span class="icon_short">
342 343 <img src="${h.url('/images/icons/heart.png')}" alt="${_('Followers')}" />
343 344 </span>
344 345 <span id="current_followers_count" class="short">${c.repository_followers}</span>
345 346 </a>
346 347 </li>
347 348 <li>
348 349 <a title="${_('Forks')}" href="${h.url('repo_forks_home',repo_name=c.repo_name)}">
349 350 <span class="icon_short">
350 351 <img src="${h.url('/images/icons/arrow_divide.png')}" alt="${_('Forks')}" />
351 352 </span>
352 353 <span class="short">${c.repository_forks}</span>
353 354 </a>
354 355 </li>
355 356
356 357 </ul>
357 358 %else:
358 359 ##ROOT MENU
359 360 <ul id="quick">
360 361 <li>
361 362 <a title="${_('Home')}" href="${h.url('home')}">
362 363 <span class="icon">
363 364 <img src="${h.url('/images/icons/home_16.png')}" alt="${_('Home')}" />
364 365 </span>
365 366 <span>${_('Home')}</span>
366 367 </a>
367 368 </li>
368 369 % if c.rhodecode_user.username != 'default':
369 370 <li>
370 371 <a title="${_('Journal')}" href="${h.url('journal')}">
371 372 <span class="icon">
372 373 <img src="${h.url('/images/icons/book.png')}" alt="${_('Journal')}" />
373 374 </span>
374 375 <span>${_('Journal')}</span>
375 376 </a>
376 377 </li>
377 378 % endif
378 379 <li>
379 380 <a title="${_('Search')}" href="${h.url('search')}">
380 381 <span class="icon">
381 382 <img src="${h.url('/images/icons/search_16.png')}" alt="${_('Search')}" />
382 383 </span>
383 384 <span>${_('Search')}</span>
384 385 </a>
385 386 </li>
386 387
387 388 %if h.HasPermissionAll('hg.admin')('access admin main page'):
388 389 <li ${is_current('admin')}>
389 390 <a title="${_('Admin')}" href="${h.url('admin_home')}">
390 391 <span class="icon">
391 392 <img src="${h.url('/images/icons/cog_edit.png')}" alt="${_('Admin')}" />
392 393 </span>
393 394 <span>${_('Admin')}</span>
394 395 </a>
395 396 ${admin_menu()}
396 397 </li>
397 398 %endif
398 399 </ul>
399 400 %endif
400 401 </%def> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now