##// END OF EJS Templates
helpers: show boolean value of value as icon, not just the True and False singletons
Mads Kiilerich -
r3627:32cb8d45 beta
parent child Browse files
Show More
@@ -1,362 +1,362 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.users
3 rhodecode.controllers.admin.users
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Users crud controller for pylons
6 Users crud controller for pylons
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
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
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
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
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/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from pylons import response
29 from pylons import response
30
30
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from pylons import request, session, tmpl_context as c, url, config
32 from pylons import request, session, tmpl_context as c, url, config
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.lib.exceptions import DefaultUserException, \
37 from rhodecode.lib.exceptions import DefaultUserException, \
38 UserOwnsReposException
38 UserOwnsReposException
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
41 AuthUser
41 AuthUser
42 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.base import BaseController, render
43
43
44 from rhodecode.model.db import User, UserEmailMap, UserIpMap
44 from rhodecode.model.db import User, UserEmailMap, UserIpMap
45 from rhodecode.model.forms import UserForm
45 from rhodecode.model.forms import UserForm
46 from rhodecode.model.user import UserModel
46 from rhodecode.model.user import UserModel
47 from rhodecode.model.meta import Session
47 from rhodecode.model.meta import Session
48 from rhodecode.lib.utils import action_logger
48 from rhodecode.lib.utils import action_logger
49 from rhodecode.lib.compat import json
49 from rhodecode.lib.compat import json
50 from rhodecode.lib.utils2 import datetime_to_time, str2bool
50 from rhodecode.lib.utils2 import datetime_to_time, str2bool
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class UsersController(BaseController):
55 class UsersController(BaseController):
56 """REST Controller styled on the Atom Publishing Protocol"""
56 """REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
58 # file has a resource setup:
59 # map.resource('user', 'users')
59 # map.resource('user', 'users')
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
62 @HasPermissionAllDecorator('hg.admin')
63 def __before__(self):
63 def __before__(self):
64 c.admin_user = session.get('admin_user')
64 c.admin_user = session.get('admin_user')
65 c.admin_username = session.get('admin_username')
65 c.admin_username = session.get('admin_username')
66 super(UsersController, self).__before__()
66 super(UsersController, self).__before__()
67 c.available_permissions = config['available_permissions']
67 c.available_permissions = config['available_permissions']
68
68
69 def index(self, format='html'):
69 def index(self, format='html'):
70 """GET /users: All items in the collection"""
70 """GET /users: All items in the collection"""
71 # url('users')
71 # url('users')
72
72
73 c.users_list = User.query().order_by(User.username).all()
73 c.users_list = User.query().order_by(User.username).all()
74
74
75 users_data = []
75 users_data = []
76 total_records = len(c.users_list)
76 total_records = len(c.users_list)
77 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
77 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
78 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
78 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
79
79
80 grav_tmpl = lambda user_email, size: (
80 grav_tmpl = lambda user_email, size: (
81 template.get_def("user_gravatar")
81 template.get_def("user_gravatar")
82 .render(user_email, size, _=_, h=h, c=c))
82 .render(user_email, size, _=_, h=h, c=c))
83
83
84 user_lnk = lambda user_id, username: (
84 user_lnk = lambda user_id, username: (
85 template.get_def("user_name")
85 template.get_def("user_name")
86 .render(user_id, username, _=_, h=h, c=c))
86 .render(user_id, username, _=_, h=h, c=c))
87
87
88 user_actions = lambda user_id, username: (
88 user_actions = lambda user_id, username: (
89 template.get_def("user_actions")
89 template.get_def("user_actions")
90 .render(user_id, username, _=_, h=h, c=c))
90 .render(user_id, username, _=_, h=h, c=c))
91
91
92 for user in c.users_list:
92 for user in c.users_list:
93
93
94 users_data.append({
94 users_data.append({
95 "gravatar": grav_tmpl(user. email, 24),
95 "gravatar": grav_tmpl(user. email, 24),
96 "raw_username": user.username,
96 "raw_username": user.username,
97 "username": user_lnk(user.user_id, user.username),
97 "username": user_lnk(user.user_id, user.username),
98 "firstname": user.name,
98 "firstname": user.name,
99 "lastname": user.lastname,
99 "lastname": user.lastname,
100 "last_login": h.fmt_date(user.last_login),
100 "last_login": h.fmt_date(user.last_login),
101 "last_login_raw": datetime_to_time(user.last_login),
101 "last_login_raw": datetime_to_time(user.last_login),
102 "active": h.bool2icon(user.active),
102 "active": h.boolicon(user.active),
103 "admin": h.bool2icon(user.admin),
103 "admin": h.boolicon(user.admin),
104 "ldap": h.bool2icon(bool(user.ldap_dn)),
104 "ldap": h.boolicon(bool(user.ldap_dn)),
105 "action": user_actions(user.user_id, user.username),
105 "action": user_actions(user.user_id, user.username),
106 })
106 })
107
107
108 c.data = json.dumps({
108 c.data = json.dumps({
109 "totalRecords": total_records,
109 "totalRecords": total_records,
110 "startIndex": 0,
110 "startIndex": 0,
111 "sort": None,
111 "sort": None,
112 "dir": "asc",
112 "dir": "asc",
113 "records": users_data
113 "records": users_data
114 })
114 })
115
115
116 return render('admin/users/users.html')
116 return render('admin/users/users.html')
117
117
118 def create(self):
118 def create(self):
119 """POST /users: Create a new item"""
119 """POST /users: Create a new item"""
120 # url('users')
120 # url('users')
121
121
122 user_model = UserModel()
122 user_model = UserModel()
123 user_form = UserForm()()
123 user_form = UserForm()()
124 try:
124 try:
125 form_result = user_form.to_python(dict(request.POST))
125 form_result = user_form.to_python(dict(request.POST))
126 user_model.create(form_result)
126 user_model.create(form_result)
127 usr = form_result['username']
127 usr = form_result['username']
128 action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr,
128 action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr,
129 None, self.ip_addr, self.sa)
129 None, self.ip_addr, self.sa)
130 h.flash(_('Created user %s') % usr,
130 h.flash(_('Created user %s') % usr,
131 category='success')
131 category='success')
132 Session().commit()
132 Session().commit()
133 except formencode.Invalid, errors:
133 except formencode.Invalid, errors:
134 return htmlfill.render(
134 return htmlfill.render(
135 render('admin/users/user_add.html'),
135 render('admin/users/user_add.html'),
136 defaults=errors.value,
136 defaults=errors.value,
137 errors=errors.error_dict or {},
137 errors=errors.error_dict or {},
138 prefix_error=False,
138 prefix_error=False,
139 encoding="UTF-8")
139 encoding="UTF-8")
140 except Exception:
140 except Exception:
141 log.error(traceback.format_exc())
141 log.error(traceback.format_exc())
142 h.flash(_('Error occurred during creation of user %s') \
142 h.flash(_('Error occurred during creation of user %s') \
143 % request.POST.get('username'), category='error')
143 % request.POST.get('username'), category='error')
144 return redirect(url('users'))
144 return redirect(url('users'))
145
145
146 def new(self, format='html'):
146 def new(self, format='html'):
147 """GET /users/new: Form to create a new item"""
147 """GET /users/new: Form to create a new item"""
148 # url('new_user')
148 # url('new_user')
149 return render('admin/users/user_add.html')
149 return render('admin/users/user_add.html')
150
150
151 def update(self, id):
151 def update(self, id):
152 """PUT /users/id: Update an existing item"""
152 """PUT /users/id: Update an existing item"""
153 # Forms posted to this method should contain a hidden field:
153 # Forms posted to this method should contain a hidden field:
154 # <input type="hidden" name="_method" value="PUT" />
154 # <input type="hidden" name="_method" value="PUT" />
155 # Or using helpers:
155 # Or using helpers:
156 # h.form(url('update_user', id=ID),
156 # h.form(url('update_user', id=ID),
157 # method='put')
157 # method='put')
158 # url('user', id=ID)
158 # url('user', id=ID)
159 user_model = UserModel()
159 user_model = UserModel()
160 c.user = user_model.get(id)
160 c.user = user_model.get(id)
161 c.ldap_dn = c.user.ldap_dn
161 c.ldap_dn = c.user.ldap_dn
162 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
162 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
163 _form = UserForm(edit=True, old_data={'user_id': id,
163 _form = UserForm(edit=True, old_data={'user_id': id,
164 'email': c.user.email})()
164 'email': c.user.email})()
165 form_result = {}
165 form_result = {}
166 try:
166 try:
167 form_result = _form.to_python(dict(request.POST))
167 form_result = _form.to_python(dict(request.POST))
168 skip_attrs = []
168 skip_attrs = []
169 if c.ldap_dn:
169 if c.ldap_dn:
170 #forbid updating username for ldap accounts
170 #forbid updating username for ldap accounts
171 skip_attrs = ['username']
171 skip_attrs = ['username']
172 user_model.update(id, form_result, skip_attrs=skip_attrs)
172 user_model.update(id, form_result, skip_attrs=skip_attrs)
173 usr = form_result['username']
173 usr = form_result['username']
174 action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
174 action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr,
175 None, self.ip_addr, self.sa)
175 None, self.ip_addr, self.sa)
176 h.flash(_('User updated successfully'), category='success')
176 h.flash(_('User updated successfully'), category='success')
177 Session().commit()
177 Session().commit()
178 except formencode.Invalid, errors:
178 except formencode.Invalid, errors:
179 c.user_email_map = UserEmailMap.query()\
179 c.user_email_map = UserEmailMap.query()\
180 .filter(UserEmailMap.user == c.user).all()
180 .filter(UserEmailMap.user == c.user).all()
181 c.user_ip_map = UserIpMap.query()\
181 c.user_ip_map = UserIpMap.query()\
182 .filter(UserIpMap.user == c.user).all()
182 .filter(UserIpMap.user == c.user).all()
183 defaults = errors.value
183 defaults = errors.value
184 e = errors.error_dict or {}
184 e = errors.error_dict or {}
185 defaults.update({
185 defaults.update({
186 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
186 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
187 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
187 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
188 '_method': 'put'
188 '_method': 'put'
189 })
189 })
190 return htmlfill.render(
190 return htmlfill.render(
191 render('admin/users/user_edit.html'),
191 render('admin/users/user_edit.html'),
192 defaults=defaults,
192 defaults=defaults,
193 errors=e,
193 errors=e,
194 prefix_error=False,
194 prefix_error=False,
195 encoding="UTF-8")
195 encoding="UTF-8")
196 except Exception:
196 except Exception:
197 log.error(traceback.format_exc())
197 log.error(traceback.format_exc())
198 h.flash(_('Error occurred during update of user %s') \
198 h.flash(_('Error occurred during update of user %s') \
199 % form_result.get('username'), category='error')
199 % form_result.get('username'), category='error')
200 return redirect(url('edit_user', id=id))
200 return redirect(url('edit_user', id=id))
201
201
202 def delete(self, id):
202 def delete(self, id):
203 """DELETE /users/id: Delete an existing item"""
203 """DELETE /users/id: Delete an existing item"""
204 # Forms posted to this method should contain a hidden field:
204 # Forms posted to this method should contain a hidden field:
205 # <input type="hidden" name="_method" value="DELETE" />
205 # <input type="hidden" name="_method" value="DELETE" />
206 # Or using helpers:
206 # Or using helpers:
207 # h.form(url('delete_user', id=ID),
207 # h.form(url('delete_user', id=ID),
208 # method='delete')
208 # method='delete')
209 # url('user', id=ID)
209 # url('user', id=ID)
210 usr = User.get_or_404(id)
210 usr = User.get_or_404(id)
211 try:
211 try:
212 UserModel().delete(usr)
212 UserModel().delete(usr)
213 Session().commit()
213 Session().commit()
214 h.flash(_('Successfully deleted user'), category='success')
214 h.flash(_('Successfully deleted user'), category='success')
215 except (UserOwnsReposException, DefaultUserException), e:
215 except (UserOwnsReposException, DefaultUserException), e:
216 h.flash(e, category='warning')
216 h.flash(e, category='warning')
217 except Exception:
217 except Exception:
218 log.error(traceback.format_exc())
218 log.error(traceback.format_exc())
219 h.flash(_('An error occurred during deletion of user'),
219 h.flash(_('An error occurred during deletion of user'),
220 category='error')
220 category='error')
221 return redirect(url('users'))
221 return redirect(url('users'))
222
222
223 def show(self, id, format='html'):
223 def show(self, id, format='html'):
224 """GET /users/id: Show a specific item"""
224 """GET /users/id: Show a specific item"""
225 # url('user', id=ID)
225 # url('user', id=ID)
226
226
227 def edit(self, id, format='html'):
227 def edit(self, id, format='html'):
228 """GET /users/id/edit: Form to edit an existing item"""
228 """GET /users/id/edit: Form to edit an existing item"""
229 # url('edit_user', id=ID)
229 # url('edit_user', id=ID)
230 c.user = User.get_or_404(id)
230 c.user = User.get_or_404(id)
231
231
232 if c.user.username == 'default':
232 if c.user.username == 'default':
233 h.flash(_("You can't edit this user"), category='warning')
233 h.flash(_("You can't edit this user"), category='warning')
234 return redirect(url('users'))
234 return redirect(url('users'))
235
235
236 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
236 c.perm_user = AuthUser(user_id=id, ip_addr=self.ip_addr)
237 c.user.permissions = {}
237 c.user.permissions = {}
238 c.granted_permissions = UserModel().fill_perms(c.user)\
238 c.granted_permissions = UserModel().fill_perms(c.user)\
239 .permissions['global']
239 .permissions['global']
240 c.user_email_map = UserEmailMap.query()\
240 c.user_email_map = UserEmailMap.query()\
241 .filter(UserEmailMap.user == c.user).all()
241 .filter(UserEmailMap.user == c.user).all()
242 c.user_ip_map = UserIpMap.query()\
242 c.user_ip_map = UserIpMap.query()\
243 .filter(UserIpMap.user == c.user).all()
243 .filter(UserIpMap.user == c.user).all()
244 user_model = UserModel()
244 user_model = UserModel()
245 c.ldap_dn = c.user.ldap_dn
245 c.ldap_dn = c.user.ldap_dn
246 defaults = c.user.get_dict()
246 defaults = c.user.get_dict()
247 defaults.update({
247 defaults.update({
248 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
248 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'),
249 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
249 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'),
250 })
250 })
251
251
252 return htmlfill.render(
252 return htmlfill.render(
253 render('admin/users/user_edit.html'),
253 render('admin/users/user_edit.html'),
254 defaults=defaults,
254 defaults=defaults,
255 encoding="UTF-8",
255 encoding="UTF-8",
256 force_defaults=False
256 force_defaults=False
257 )
257 )
258
258
259 def update_perm(self, id):
259 def update_perm(self, id):
260 """PUT /users_perm/id: Update an existing item"""
260 """PUT /users_perm/id: Update an existing item"""
261 # url('user_perm', id=ID, method='put')
261 # url('user_perm', id=ID, method='put')
262 usr = User.get_or_404(id)
262 usr = User.get_or_404(id)
263 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
263 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
264 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
264 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
265 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
265 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
266
266
267 user_model = UserModel()
267 user_model = UserModel()
268
268
269 try:
269 try:
270 usr.inherit_default_permissions = inherit_perms
270 usr.inherit_default_permissions = inherit_perms
271 Session().add(usr)
271 Session().add(usr)
272
272
273 if grant_create_perm:
273 if grant_create_perm:
274 user_model.revoke_perm(usr, 'hg.create.none')
274 user_model.revoke_perm(usr, 'hg.create.none')
275 user_model.grant_perm(usr, 'hg.create.repository')
275 user_model.grant_perm(usr, 'hg.create.repository')
276 h.flash(_("Granted 'repository create' permission to user"),
276 h.flash(_("Granted 'repository create' permission to user"),
277 category='success')
277 category='success')
278 else:
278 else:
279 user_model.revoke_perm(usr, 'hg.create.repository')
279 user_model.revoke_perm(usr, 'hg.create.repository')
280 user_model.grant_perm(usr, 'hg.create.none')
280 user_model.grant_perm(usr, 'hg.create.none')
281 h.flash(_("Revoked 'repository create' permission to user"),
281 h.flash(_("Revoked 'repository create' permission to user"),
282 category='success')
282 category='success')
283
283
284 if grant_fork_perm:
284 if grant_fork_perm:
285 user_model.revoke_perm(usr, 'hg.fork.none')
285 user_model.revoke_perm(usr, 'hg.fork.none')
286 user_model.grant_perm(usr, 'hg.fork.repository')
286 user_model.grant_perm(usr, 'hg.fork.repository')
287 h.flash(_("Granted 'repository fork' permission to user"),
287 h.flash(_("Granted 'repository fork' permission to user"),
288 category='success')
288 category='success')
289 else:
289 else:
290 user_model.revoke_perm(usr, 'hg.fork.repository')
290 user_model.revoke_perm(usr, 'hg.fork.repository')
291 user_model.grant_perm(usr, 'hg.fork.none')
291 user_model.grant_perm(usr, 'hg.fork.none')
292 h.flash(_("Revoked 'repository fork' permission to user"),
292 h.flash(_("Revoked 'repository fork' permission to user"),
293 category='success')
293 category='success')
294
294
295 Session().commit()
295 Session().commit()
296 except Exception:
296 except Exception:
297 log.error(traceback.format_exc())
297 log.error(traceback.format_exc())
298 h.flash(_('An error occurred during permissions saving'),
298 h.flash(_('An error occurred during permissions saving'),
299 category='error')
299 category='error')
300 return redirect(url('edit_user', id=id))
300 return redirect(url('edit_user', id=id))
301
301
302 def add_email(self, id):
302 def add_email(self, id):
303 """POST /user_emails:Add an existing item"""
303 """POST /user_emails:Add an existing item"""
304 # url('user_emails', id=ID, method='put')
304 # url('user_emails', id=ID, method='put')
305
305
306 email = request.POST.get('new_email')
306 email = request.POST.get('new_email')
307 user_model = UserModel()
307 user_model = UserModel()
308
308
309 try:
309 try:
310 user_model.add_extra_email(id, email)
310 user_model.add_extra_email(id, email)
311 Session().commit()
311 Session().commit()
312 h.flash(_("Added email %s to user") % email, category='success')
312 h.flash(_("Added email %s to user") % email, category='success')
313 except formencode.Invalid, error:
313 except formencode.Invalid, error:
314 msg = error.error_dict['email']
314 msg = error.error_dict['email']
315 h.flash(msg, category='error')
315 h.flash(msg, category='error')
316 except Exception:
316 except Exception:
317 log.error(traceback.format_exc())
317 log.error(traceback.format_exc())
318 h.flash(_('An error occurred during email saving'),
318 h.flash(_('An error occurred during email saving'),
319 category='error')
319 category='error')
320 return redirect(url('edit_user', id=id))
320 return redirect(url('edit_user', id=id))
321
321
322 def delete_email(self, id):
322 def delete_email(self, id):
323 """DELETE /user_emails_delete/id: Delete an existing item"""
323 """DELETE /user_emails_delete/id: Delete an existing item"""
324 # url('user_emails_delete', id=ID, method='delete')
324 # url('user_emails_delete', id=ID, method='delete')
325 user_model = UserModel()
325 user_model = UserModel()
326 user_model.delete_extra_email(id, request.POST.get('del_email'))
326 user_model.delete_extra_email(id, request.POST.get('del_email'))
327 Session().commit()
327 Session().commit()
328 h.flash(_("Removed email from user"), category='success')
328 h.flash(_("Removed email from user"), category='success')
329 return redirect(url('edit_user', id=id))
329 return redirect(url('edit_user', id=id))
330
330
331 def add_ip(self, id):
331 def add_ip(self, id):
332 """POST /user_ips:Add an existing item"""
332 """POST /user_ips:Add an existing item"""
333 # url('user_ips', id=ID, method='put')
333 # url('user_ips', id=ID, method='put')
334
334
335 ip = request.POST.get('new_ip')
335 ip = request.POST.get('new_ip')
336 user_model = UserModel()
336 user_model = UserModel()
337
337
338 try:
338 try:
339 user_model.add_extra_ip(id, ip)
339 user_model.add_extra_ip(id, ip)
340 Session().commit()
340 Session().commit()
341 h.flash(_("Added ip %s to user") % ip, category='success')
341 h.flash(_("Added ip %s to user") % ip, category='success')
342 except formencode.Invalid, error:
342 except formencode.Invalid, error:
343 msg = error.error_dict['ip']
343 msg = error.error_dict['ip']
344 h.flash(msg, category='error')
344 h.flash(msg, category='error')
345 except Exception:
345 except Exception:
346 log.error(traceback.format_exc())
346 log.error(traceback.format_exc())
347 h.flash(_('An error occurred during ip saving'),
347 h.flash(_('An error occurred during ip saving'),
348 category='error')
348 category='error')
349 if 'default_user' in request.POST:
349 if 'default_user' in request.POST:
350 return redirect(url('edit_permission', id='default'))
350 return redirect(url('edit_permission', id='default'))
351 return redirect(url('edit_user', id=id))
351 return redirect(url('edit_user', id=id))
352
352
353 def delete_ip(self, id):
353 def delete_ip(self, id):
354 """DELETE /user_ips_delete/id: Delete an existing item"""
354 """DELETE /user_ips_delete/id: Delete an existing item"""
355 # url('user_ips_delete', id=ID, method='delete')
355 # url('user_ips_delete', id=ID, method='delete')
356 user_model = UserModel()
356 user_model = UserModel()
357 user_model.delete_extra_ip(id, request.POST.get('del_ip'))
357 user_model.delete_extra_ip(id, request.POST.get('del_ip'))
358 Session().commit()
358 Session().commit()
359 h.flash(_("Removed ip from user"), category='success')
359 h.flash(_("Removed ip from user"), category='success')
360 if 'default_user' in request.POST:
360 if 'default_user' in request.POST:
361 return redirect(url('edit_permission', id='default'))
361 return redirect(url('edit_permission', id='default'))
362 return redirect(url('edit_user', id=id))
362 return redirect(url('edit_user', id=id))
@@ -1,1210 +1,1207 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
47 safe_int
47 safe_int
48 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
52 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.db import URL_SEP, Permission
53 from rhodecode.model.db import URL_SEP, Permission
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 html_escape_table = {
58 html_escape_table = {
59 "&": "&amp;",
59 "&": "&amp;",
60 '"': "&quot;",
60 '"': "&quot;",
61 "'": "&apos;",
61 "'": "&apos;",
62 ">": "&gt;",
62 ">": "&gt;",
63 "<": "&lt;",
63 "<": "&lt;",
64 }
64 }
65
65
66
66
67 def html_escape(text):
67 def html_escape(text):
68 """Produce entities within text."""
68 """Produce entities within text."""
69 return "".join(html_escape_table.get(c, c) for c in text)
69 return "".join(html_escape_table.get(c, c) for c in text)
70
70
71
71
72 def shorter(text, size=20):
72 def shorter(text, size=20):
73 postfix = '...'
73 postfix = '...'
74 if len(text) > size:
74 if len(text) > size:
75 return text[:size - len(postfix)] + postfix
75 return text[:size - len(postfix)] + postfix
76 return text
76 return text
77
77
78
78
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
80 """
80 """
81 Reset button
81 Reset button
82 """
82 """
83 _set_input_attrs(attrs, type, name, value)
83 _set_input_attrs(attrs, type, name, value)
84 _set_id_attr(attrs, id, name)
84 _set_id_attr(attrs, id, name)
85 convert_boolean_attrs(attrs, ["disabled"])
85 convert_boolean_attrs(attrs, ["disabled"])
86 return HTML.input(**attrs)
86 return HTML.input(**attrs)
87
87
88 reset = _reset
88 reset = _reset
89 safeid = _make_safe_id_component
89 safeid = _make_safe_id_component
90
90
91
91
92 def FID(raw_id, path):
92 def FID(raw_id, path):
93 """
93 """
94 Creates a uniqe ID for filenode based on it's hash of path and revision
94 Creates a uniqe ID for filenode based on it's hash of path and revision
95 it's safe to use in urls
95 it's safe to use in urls
96
96
97 :param raw_id:
97 :param raw_id:
98 :param path:
98 :param path:
99 """
99 """
100
100
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
102
102
103
103
104 def get_token():
104 def get_token():
105 """Return the current authentication token, creating one if one doesn't
105 """Return the current authentication token, creating one if one doesn't
106 already exist.
106 already exist.
107 """
107 """
108 token_key = "_authentication_token"
108 token_key = "_authentication_token"
109 from pylons import session
109 from pylons import session
110 if not token_key in session:
110 if not token_key in session:
111 try:
111 try:
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
113 except AttributeError: # Python < 2.4
113 except AttributeError: # Python < 2.4
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
115 session[token_key] = token
115 session[token_key] = token
116 if hasattr(session, 'save'):
116 if hasattr(session, 'save'):
117 session.save()
117 session.save()
118 return session[token_key]
118 return session[token_key]
119
119
120
120
121 class _GetError(object):
121 class _GetError(object):
122 """Get error from form_errors, and represent it as span wrapped error
122 """Get error from form_errors, and represent it as span wrapped error
123 message
123 message
124
124
125 :param field_name: field to fetch errors for
125 :param field_name: field to fetch errors for
126 :param form_errors: form errors dict
126 :param form_errors: form errors dict
127 """
127 """
128
128
129 def __call__(self, field_name, form_errors):
129 def __call__(self, field_name, form_errors):
130 tmpl = """<span class="error_msg">%s</span>"""
130 tmpl = """<span class="error_msg">%s</span>"""
131 if form_errors and field_name in form_errors:
131 if form_errors and field_name in form_errors:
132 return literal(tmpl % form_errors.get(field_name))
132 return literal(tmpl % form_errors.get(field_name))
133
133
134 get_error = _GetError()
134 get_error = _GetError()
135
135
136
136
137 class _ToolTip(object):
137 class _ToolTip(object):
138
138
139 def __call__(self, tooltip_title, trim_at=50):
139 def __call__(self, tooltip_title, trim_at=50):
140 """
140 """
141 Special function just to wrap our text into nice formatted
141 Special function just to wrap our text into nice formatted
142 autowrapped text
142 autowrapped text
143
143
144 :param tooltip_title:
144 :param tooltip_title:
145 """
145 """
146 tooltip_title = escape(tooltip_title)
146 tooltip_title = escape(tooltip_title)
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
148 return tooltip_title
148 return tooltip_title
149 tooltip = _ToolTip()
149 tooltip = _ToolTip()
150
150
151
151
152 class _FilesBreadCrumbs(object):
152 class _FilesBreadCrumbs(object):
153
153
154 def __call__(self, repo_name, rev, paths):
154 def __call__(self, repo_name, rev, paths):
155 if isinstance(paths, str):
155 if isinstance(paths, str):
156 paths = safe_unicode(paths)
156 paths = safe_unicode(paths)
157 url_l = [link_to(repo_name, url('files_home',
157 url_l = [link_to(repo_name, url('files_home',
158 repo_name=repo_name,
158 repo_name=repo_name,
159 revision=rev, f_path=''),
159 revision=rev, f_path=''),
160 class_='ypjax-link')]
160 class_='ypjax-link')]
161 paths_l = paths.split('/')
161 paths_l = paths.split('/')
162 for cnt, p in enumerate(paths_l):
162 for cnt, p in enumerate(paths_l):
163 if p != '':
163 if p != '':
164 url_l.append(link_to(p,
164 url_l.append(link_to(p,
165 url('files_home',
165 url('files_home',
166 repo_name=repo_name,
166 repo_name=repo_name,
167 revision=rev,
167 revision=rev,
168 f_path='/'.join(paths_l[:cnt + 1])
168 f_path='/'.join(paths_l[:cnt + 1])
169 ),
169 ),
170 class_='ypjax-link'
170 class_='ypjax-link'
171 )
171 )
172 )
172 )
173
173
174 return literal('/'.join(url_l))
174 return literal('/'.join(url_l))
175
175
176 files_breadcrumbs = _FilesBreadCrumbs()
176 files_breadcrumbs = _FilesBreadCrumbs()
177
177
178
178
179 class CodeHtmlFormatter(HtmlFormatter):
179 class CodeHtmlFormatter(HtmlFormatter):
180 """
180 """
181 My code Html Formatter for source codes
181 My code Html Formatter for source codes
182 """
182 """
183
183
184 def wrap(self, source, outfile):
184 def wrap(self, source, outfile):
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
186
186
187 def _wrap_code(self, source):
187 def _wrap_code(self, source):
188 for cnt, it in enumerate(source):
188 for cnt, it in enumerate(source):
189 i, t = it
189 i, t = it
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
191 yield i, t
191 yield i, t
192
192
193 def _wrap_tablelinenos(self, inner):
193 def _wrap_tablelinenos(self, inner):
194 dummyoutfile = StringIO.StringIO()
194 dummyoutfile = StringIO.StringIO()
195 lncount = 0
195 lncount = 0
196 for t, line in inner:
196 for t, line in inner:
197 if t:
197 if t:
198 lncount += 1
198 lncount += 1
199 dummyoutfile.write(line)
199 dummyoutfile.write(line)
200
200
201 fl = self.linenostart
201 fl = self.linenostart
202 mw = len(str(lncount + fl - 1))
202 mw = len(str(lncount + fl - 1))
203 sp = self.linenospecial
203 sp = self.linenospecial
204 st = self.linenostep
204 st = self.linenostep
205 la = self.lineanchors
205 la = self.lineanchors
206 aln = self.anchorlinenos
206 aln = self.anchorlinenos
207 nocls = self.noclasses
207 nocls = self.noclasses
208 if sp:
208 if sp:
209 lines = []
209 lines = []
210
210
211 for i in range(fl, fl + lncount):
211 for i in range(fl, fl + lncount):
212 if i % st == 0:
212 if i % st == 0:
213 if i % sp == 0:
213 if i % sp == 0:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
216 (la, i, mw, i))
216 (la, i, mw, i))
217 else:
217 else:
218 lines.append('<span class="special">%*d</span>' % (mw, i))
218 lines.append('<span class="special">%*d</span>' % (mw, i))
219 else:
219 else:
220 if aln:
220 if aln:
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
222 else:
222 else:
223 lines.append('%*d' % (mw, i))
223 lines.append('%*d' % (mw, i))
224 else:
224 else:
225 lines.append('')
225 lines.append('')
226 ls = '\n'.join(lines)
226 ls = '\n'.join(lines)
227 else:
227 else:
228 lines = []
228 lines = []
229 for i in range(fl, fl + lncount):
229 for i in range(fl, fl + lncount):
230 if i % st == 0:
230 if i % st == 0:
231 if aln:
231 if aln:
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
233 else:
233 else:
234 lines.append('%*d' % (mw, i))
234 lines.append('%*d' % (mw, i))
235 else:
235 else:
236 lines.append('')
236 lines.append('')
237 ls = '\n'.join(lines)
237 ls = '\n'.join(lines)
238
238
239 # in case you wonder about the seemingly redundant <div> here: since the
239 # in case you wonder about the seemingly redundant <div> here: since the
240 # content in the other cell also is wrapped in a div, some browsers in
240 # content in the other cell also is wrapped in a div, some browsers in
241 # some configurations seem to mess up the formatting...
241 # some configurations seem to mess up the formatting...
242 if nocls:
242 if nocls:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td><div class="linenodiv" '
244 '<tr><td><div class="linenodiv" '
245 'style="background-color: #f0f0f0; padding-right: 10px">'
245 'style="background-color: #f0f0f0; padding-right: 10px">'
246 '<pre style="line-height: 125%">' +
246 '<pre style="line-height: 125%">' +
247 ls + '</pre></div></td><td id="hlcode" class="code">')
247 ls + '</pre></div></td><td id="hlcode" class="code">')
248 else:
248 else:
249 yield 0, ('<table class="%stable">' % self.cssclass +
249 yield 0, ('<table class="%stable">' % self.cssclass +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
251 ls + '</pre></div></td><td id="hlcode" class="code">')
251 ls + '</pre></div></td><td id="hlcode" class="code">')
252 yield 0, dummyoutfile.getvalue()
252 yield 0, dummyoutfile.getvalue()
253 yield 0, '</td></tr></table>'
253 yield 0, '</td></tr></table>'
254
254
255
255
256 def pygmentize(filenode, **kwargs):
256 def pygmentize(filenode, **kwargs):
257 """
257 """
258 pygmentize function using pygments
258 pygmentize function using pygments
259
259
260 :param filenode:
260 :param filenode:
261 """
261 """
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
263 return literal(code_highlight(filenode.content, lexer,
263 return literal(code_highlight(filenode.content, lexer,
264 CodeHtmlFormatter(**kwargs)))
264 CodeHtmlFormatter(**kwargs)))
265
265
266
266
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
268 """
268 """
269 pygmentize function for annotation
269 pygmentize function for annotation
270
270
271 :param filenode:
271 :param filenode:
272 """
272 """
273
273
274 color_dict = {}
274 color_dict = {}
275
275
276 def gen_color(n=10000):
276 def gen_color(n=10000):
277 """generator for getting n of evenly distributed colors using
277 """generator for getting n of evenly distributed colors using
278 hsv color and golden ratio. It always return same order of colors
278 hsv color and golden ratio. It always return same order of colors
279
279
280 :returns: RGB tuple
280 :returns: RGB tuple
281 """
281 """
282
282
283 def hsv_to_rgb(h, s, v):
283 def hsv_to_rgb(h, s, v):
284 if s == 0.0:
284 if s == 0.0:
285 return v, v, v
285 return v, v, v
286 i = int(h * 6.0) # XXX assume int() truncates!
286 i = int(h * 6.0) # XXX assume int() truncates!
287 f = (h * 6.0) - i
287 f = (h * 6.0) - i
288 p = v * (1.0 - s)
288 p = v * (1.0 - s)
289 q = v * (1.0 - s * f)
289 q = v * (1.0 - s * f)
290 t = v * (1.0 - s * (1.0 - f))
290 t = v * (1.0 - s * (1.0 - f))
291 i = i % 6
291 i = i % 6
292 if i == 0:
292 if i == 0:
293 return v, t, p
293 return v, t, p
294 if i == 1:
294 if i == 1:
295 return q, v, p
295 return q, v, p
296 if i == 2:
296 if i == 2:
297 return p, v, t
297 return p, v, t
298 if i == 3:
298 if i == 3:
299 return p, q, v
299 return p, q, v
300 if i == 4:
300 if i == 4:
301 return t, p, v
301 return t, p, v
302 if i == 5:
302 if i == 5:
303 return v, p, q
303 return v, p, q
304
304
305 golden_ratio = 0.618033988749895
305 golden_ratio = 0.618033988749895
306 h = 0.22717784590367374
306 h = 0.22717784590367374
307
307
308 for _ in xrange(n):
308 for _ in xrange(n):
309 h += golden_ratio
309 h += golden_ratio
310 h %= 1
310 h %= 1
311 HSV_tuple = [h, 0.95, 0.95]
311 HSV_tuple = [h, 0.95, 0.95]
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
314
314
315 cgenerator = gen_color()
315 cgenerator = gen_color()
316
316
317 def get_color_string(cs):
317 def get_color_string(cs):
318 if cs in color_dict:
318 if cs in color_dict:
319 col = color_dict[cs]
319 col = color_dict[cs]
320 else:
320 else:
321 col = color_dict[cs] = cgenerator.next()
321 col = color_dict[cs] = cgenerator.next()
322 return "color: rgb(%s)! important;" % (', '.join(col))
322 return "color: rgb(%s)! important;" % (', '.join(col))
323
323
324 def url_func(repo_name):
324 def url_func(repo_name):
325
325
326 def _url_func(changeset):
326 def _url_func(changeset):
327 author = changeset.author
327 author = changeset.author
328 date = changeset.date
328 date = changeset.date
329 message = tooltip(changeset.message)
329 message = tooltip(changeset.message)
330
330
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
333 "</b> %s<br/></div>")
333 "</b> %s<br/></div>")
334
334
335 tooltip_html = tooltip_html % (author, date, message)
335 tooltip_html = tooltip_html % (author, date, message)
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=repo_name,
340 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return _url_func
349 return _url_func
350
350
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
352
352
353
353
354 def is_following_repo(repo_name, user_id):
354 def is_following_repo(repo_name, user_id):
355 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
356 return ScmModel().is_following_repo(repo_name, user_id)
356 return ScmModel().is_following_repo(repo_name, user_id)
357
357
358 flash = _Flash()
358 flash = _Flash()
359
359
360 #==============================================================================
360 #==============================================================================
361 # SCM FILTERS available via h.
361 # SCM FILTERS available via h.
362 #==============================================================================
362 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
363 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
365 from rhodecode.model.db import User, ChangesetStatus
366
366
367 age = lambda x, y=False: _age(x, y)
367 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
368 capitalize = lambda x: x.capitalize()
369 email = author_email
369 email = author_email
370 short_id = lambda x: x[:12]
370 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
372
373
373
374 def show_id(cs):
374 def show_id(cs):
375 """
375 """
376 Configurable function that shows ID
376 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
377 by default it's r123:fffeeefffeee
378
378
379 :param cs: changeset instance
379 :param cs: changeset instance
380 """
380 """
381 from rhodecode import CONFIG
381 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('sha_len', 12))
382 def_len = safe_int(CONFIG.get('sha_len', 12))
383 show_rev = str2bool(CONFIG.get('sha_show_numeric_rev', True))
383 show_rev = str2bool(CONFIG.get('sha_show_numeric_rev', True))
384
384
385 raw_id = cs.raw_id[:def_len]
385 raw_id = cs.raw_id[:def_len]
386 if show_rev:
386 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
387 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
388 else:
389 return '%s' % (raw_id)
389 return '%s' % (raw_id)
390
390
391
391
392 def fmt_date(date):
392 def fmt_date(date):
393 if date:
393 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
395 return date.strftime(_fmt).decode('utf8')
396
396
397 return ""
397 return ""
398
398
399
399
400 def is_git(repository):
400 def is_git(repository):
401 if hasattr(repository, 'alias'):
401 if hasattr(repository, 'alias'):
402 _type = repository.alias
402 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
403 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
404 _type = repository.repo_type
405 else:
405 else:
406 _type = repository
406 _type = repository
407 return _type == 'git'
407 return _type == 'git'
408
408
409
409
410 def is_hg(repository):
410 def is_hg(repository):
411 if hasattr(repository, 'alias'):
411 if hasattr(repository, 'alias'):
412 _type = repository.alias
412 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
413 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
414 _type = repository.repo_type
415 else:
415 else:
416 _type = repository
416 _type = repository
417 return _type == 'hg'
417 return _type == 'hg'
418
418
419
419
420 def email_or_none(author):
420 def email_or_none(author):
421 # extract email from the commit string
421 # extract email from the commit string
422 _email = email(author)
422 _email = email(author)
423 if _email != '':
423 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
424 # check it against RhodeCode database, and use the MAIN email for this
425 # user
425 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return user.email
428 return user.email
429 return _email
429 return _email
430
430
431 # See if it contains a username we can get an email from
431 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
432 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
433 cache=True)
434 if user is not None:
434 if user is not None:
435 return user.email
435 return user.email
436
436
437 # No valid email, not a valid user in the system, none!
437 # No valid email, not a valid user in the system, none!
438 return None
438 return None
439
439
440
440
441 def person(author, show_attr="username_and_name"):
441 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
442 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
443 person_getter = lambda usr: getattr(usr, show_attr)
444
444
445 # Valid email in the attribute passed, see if they're in the system
445 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
446 _email = email(author)
447 if _email != '':
447 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
449 if user is not None:
450 return person_getter(user)
450 return person_getter(user)
451 return _email
451 return _email
452
452
453 # Maybe it's a username?
453 # Maybe it's a username?
454 _author = author_name(author)
454 _author = author_name(author)
455 user = User.get_by_username(_author, case_insensitive=True,
455 user = User.get_by_username(_author, case_insensitive=True,
456 cache=True)
456 cache=True)
457 if user is not None:
457 if user is not None:
458 return person_getter(user)
458 return person_getter(user)
459
459
460 # Still nothing? Just pass back the author name then
460 # Still nothing? Just pass back the author name then
461 return _author
461 return _author
462
462
463
463
464 def person_by_id(id_, show_attr="username_and_name"):
464 def person_by_id(id_, show_attr="username_and_name"):
465 # attr to return from fetched user
465 # attr to return from fetched user
466 person_getter = lambda usr: getattr(usr, show_attr)
466 person_getter = lambda usr: getattr(usr, show_attr)
467
467
468 #maybe it's an ID ?
468 #maybe it's an ID ?
469 if str(id_).isdigit() or isinstance(id_, int):
469 if str(id_).isdigit() or isinstance(id_, int):
470 id_ = int(id_)
470 id_ = int(id_)
471 user = User.get(id_)
471 user = User.get(id_)
472 if user is not None:
472 if user is not None:
473 return person_getter(user)
473 return person_getter(user)
474 return id_
474 return id_
475
475
476
476
477 def desc_stylize(value):
477 def desc_stylize(value):
478 """
478 """
479 converts tags from value into html equivalent
479 converts tags from value into html equivalent
480
480
481 :param value:
481 :param value:
482 """
482 """
483 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
485 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
486 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
487 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
488 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
489 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
490 '<div class="metatag" tag="lang">\\2</div>', value)
490 '<div class="metatag" tag="lang">\\2</div>', value)
491 value = re.sub(r'\[([a-z]+)\]',
491 value = re.sub(r'\[([a-z]+)\]',
492 '<div class="metatag" tag="\\1">\\1</div>', value)
492 '<div class="metatag" tag="\\1">\\1</div>', value)
493
493
494 return value
494 return value
495
495
496
496
497 def bool2icon(value):
497 def boolicon(value):
498 """Returns True/False values represented as small html image of true/false
498 """Returns boolean value of a value, represented as small html image of true/false
499 icons
499 icons
500
500
501 :param value: bool value
501 :param value: value
502 """
502 """
503
503
504 if value is True:
504 if value:
505 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 return HTML.tag('img', src=url("/images/icons/accept.png"),
506 alt=_('True'))
506 alt=_('True'))
507
507 else:
508 if value is False:
509 return HTML.tag('img', src=url("/images/icons/cancel.png"),
508 return HTML.tag('img', src=url("/images/icons/cancel.png"),
510 alt=_('False'))
509 alt=_('False'))
511
510
512 return value
513
514
511
515 def action_parser(user_log, feed=False, parse_cs=False):
512 def action_parser(user_log, feed=False, parse_cs=False):
516 """
513 """
517 This helper will action_map the specified string action into translated
514 This helper will action_map the specified string action into translated
518 fancy names with icons and links
515 fancy names with icons and links
519
516
520 :param user_log: user log instance
517 :param user_log: user log instance
521 :param feed: use output for feeds (no html and fancy icons)
518 :param feed: use output for feeds (no html and fancy icons)
522 :param parse_cs: parse Changesets into VCS instances
519 :param parse_cs: parse Changesets into VCS instances
523 """
520 """
524
521
525 action = user_log.action
522 action = user_log.action
526 action_params = ' '
523 action_params = ' '
527
524
528 x = action.split(':')
525 x = action.split(':')
529
526
530 if len(x) > 1:
527 if len(x) > 1:
531 action, action_params = x
528 action, action_params = x
532
529
533 def get_cs_links():
530 def get_cs_links():
534 revs_limit = 3 # display this amount always
531 revs_limit = 3 # display this amount always
535 revs_top_limit = 50 # show upto this amount of changesets hidden
532 revs_top_limit = 50 # show upto this amount of changesets hidden
536 revs_ids = action_params.split(',')
533 revs_ids = action_params.split(',')
537 deleted = user_log.repository is None
534 deleted = user_log.repository is None
538 if deleted:
535 if deleted:
539 return ','.join(revs_ids)
536 return ','.join(revs_ids)
540
537
541 repo_name = user_log.repository.repo_name
538 repo_name = user_log.repository.repo_name
542
539
543 def lnk(rev, repo_name):
540 def lnk(rev, repo_name):
544 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
541 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
545 lazy_cs = True
542 lazy_cs = True
546 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
543 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
547 lazy_cs = False
544 lazy_cs = False
548 lbl = '?'
545 lbl = '?'
549 if rev.op == 'delete_branch':
546 if rev.op == 'delete_branch':
550 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
547 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
551 title = ''
548 title = ''
552 elif rev.op == 'tag':
549 elif rev.op == 'tag':
553 lbl = '%s' % _('Created tag: %s') % rev.ref_name
550 lbl = '%s' % _('Created tag: %s') % rev.ref_name
554 title = ''
551 title = ''
555 _url = '#'
552 _url = '#'
556
553
557 else:
554 else:
558 lbl = '%s' % (rev.short_id[:8])
555 lbl = '%s' % (rev.short_id[:8])
559 _url = url('changeset_home', repo_name=repo_name,
556 _url = url('changeset_home', repo_name=repo_name,
560 revision=rev.raw_id)
557 revision=rev.raw_id)
561 title = tooltip(rev.message)
558 title = tooltip(rev.message)
562 else:
559 else:
563 ## changeset cannot be found/striped/removed etc.
560 ## changeset cannot be found/striped/removed etc.
564 lbl = ('%s' % rev)[:12]
561 lbl = ('%s' % rev)[:12]
565 _url = '#'
562 _url = '#'
566 title = _('Changeset not found')
563 title = _('Changeset not found')
567 if parse_cs:
564 if parse_cs:
568 return link_to(lbl, _url, title=title, class_='tooltip')
565 return link_to(lbl, _url, title=title, class_='tooltip')
569 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
566 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
570 class_='lazy-cs' if lazy_cs else '')
567 class_='lazy-cs' if lazy_cs else '')
571
568
572 def _get_op(rev_txt):
569 def _get_op(rev_txt):
573 _op = None
570 _op = None
574 _name = rev_txt
571 _name = rev_txt
575 if len(rev_txt.split('=>')) == 2:
572 if len(rev_txt.split('=>')) == 2:
576 _op, _name = rev_txt.split('=>')
573 _op, _name = rev_txt.split('=>')
577 return _op, _name
574 return _op, _name
578
575
579 revs = []
576 revs = []
580 if len(filter(lambda v: v != '', revs_ids)) > 0:
577 if len(filter(lambda v: v != '', revs_ids)) > 0:
581 repo = None
578 repo = None
582 for rev in revs_ids[:revs_top_limit]:
579 for rev in revs_ids[:revs_top_limit]:
583 _op, _name = _get_op(rev)
580 _op, _name = _get_op(rev)
584
581
585 # we want parsed changesets, or new log store format is bad
582 # we want parsed changesets, or new log store format is bad
586 if parse_cs:
583 if parse_cs:
587 try:
584 try:
588 if repo is None:
585 if repo is None:
589 repo = user_log.repository.scm_instance
586 repo = user_log.repository.scm_instance
590 _rev = repo.get_changeset(rev)
587 _rev = repo.get_changeset(rev)
591 revs.append(_rev)
588 revs.append(_rev)
592 except ChangesetDoesNotExistError:
589 except ChangesetDoesNotExistError:
593 log.error('cannot find revision %s in this repo' % rev)
590 log.error('cannot find revision %s in this repo' % rev)
594 revs.append(rev)
591 revs.append(rev)
595 continue
592 continue
596 else:
593 else:
597 _rev = AttributeDict({
594 _rev = AttributeDict({
598 'short_id': rev[:12],
595 'short_id': rev[:12],
599 'raw_id': rev,
596 'raw_id': rev,
600 'message': '',
597 'message': '',
601 'op': _op,
598 'op': _op,
602 'ref_name': _name
599 'ref_name': _name
603 })
600 })
604 revs.append(_rev)
601 revs.append(_rev)
605 cs_links = []
602 cs_links = []
606 cs_links.append(" " + ', '.join(
603 cs_links.append(" " + ', '.join(
607 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
604 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
608 )
605 )
609 )
606 )
610 _op1, _name1 = _get_op(revs_ids[0])
607 _op1, _name1 = _get_op(revs_ids[0])
611 _op2, _name2 = _get_op(revs_ids[-1])
608 _op2, _name2 = _get_op(revs_ids[-1])
612
609
613 _rev = '%s...%s' % (_name1, _name2)
610 _rev = '%s...%s' % (_name1, _name2)
614
611
615 compare_view = (
612 compare_view = (
616 ' <div class="compare_view tooltip" title="%s">'
613 ' <div class="compare_view tooltip" title="%s">'
617 '<a href="%s">%s</a> </div>' % (
614 '<a href="%s">%s</a> </div>' % (
618 _('Show all combined changesets %s->%s') % (
615 _('Show all combined changesets %s->%s') % (
619 revs_ids[0][:12], revs_ids[-1][:12]
616 revs_ids[0][:12], revs_ids[-1][:12]
620 ),
617 ),
621 url('changeset_home', repo_name=repo_name,
618 url('changeset_home', repo_name=repo_name,
622 revision=_rev
619 revision=_rev
623 ),
620 ),
624 _('compare view')
621 _('compare view')
625 )
622 )
626 )
623 )
627
624
628 # if we have exactly one more than normally displayed
625 # if we have exactly one more than normally displayed
629 # just display it, takes less space than displaying
626 # just display it, takes less space than displaying
630 # "and 1 more revisions"
627 # "and 1 more revisions"
631 if len(revs_ids) == revs_limit + 1:
628 if len(revs_ids) == revs_limit + 1:
632 rev = revs[revs_limit]
629 rev = revs[revs_limit]
633 cs_links.append(", " + lnk(rev, repo_name))
630 cs_links.append(", " + lnk(rev, repo_name))
634
631
635 # hidden-by-default ones
632 # hidden-by-default ones
636 if len(revs_ids) > revs_limit + 1:
633 if len(revs_ids) > revs_limit + 1:
637 uniq_id = revs_ids[0]
634 uniq_id = revs_ids[0]
638 html_tmpl = (
635 html_tmpl = (
639 '<span> %s <a class="show_more" id="_%s" '
636 '<span> %s <a class="show_more" id="_%s" '
640 'href="#more">%s</a> %s</span>'
637 'href="#more">%s</a> %s</span>'
641 )
638 )
642 if not feed:
639 if not feed:
643 cs_links.append(html_tmpl % (
640 cs_links.append(html_tmpl % (
644 _('and'),
641 _('and'),
645 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
642 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
646 _('revisions')
643 _('revisions')
647 )
644 )
648 )
645 )
649
646
650 if not feed:
647 if not feed:
651 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
648 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
652 else:
649 else:
653 html_tmpl = '<span id="%s"> %s </span>'
650 html_tmpl = '<span id="%s"> %s </span>'
654
651
655 morelinks = ', '.join(
652 morelinks = ', '.join(
656 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
653 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
657 )
654 )
658
655
659 if len(revs_ids) > revs_top_limit:
656 if len(revs_ids) > revs_top_limit:
660 morelinks += ', ...'
657 morelinks += ', ...'
661
658
662 cs_links.append(html_tmpl % (uniq_id, morelinks))
659 cs_links.append(html_tmpl % (uniq_id, morelinks))
663 if len(revs) > 1:
660 if len(revs) > 1:
664 cs_links.append(compare_view)
661 cs_links.append(compare_view)
665 return ''.join(cs_links)
662 return ''.join(cs_links)
666
663
667 def get_fork_name():
664 def get_fork_name():
668 repo_name = action_params
665 repo_name = action_params
669 _url = url('summary_home', repo_name=repo_name)
666 _url = url('summary_home', repo_name=repo_name)
670 return _('fork name %s') % link_to(action_params, _url)
667 return _('fork name %s') % link_to(action_params, _url)
671
668
672 def get_user_name():
669 def get_user_name():
673 user_name = action_params
670 user_name = action_params
674 return user_name
671 return user_name
675
672
676 def get_users_group():
673 def get_users_group():
677 group_name = action_params
674 group_name = action_params
678 return group_name
675 return group_name
679
676
680 def get_pull_request():
677 def get_pull_request():
681 pull_request_id = action_params
678 pull_request_id = action_params
682 deleted = user_log.repository is None
679 deleted = user_log.repository is None
683 if deleted:
680 if deleted:
684 repo_name = user_log.repository_name
681 repo_name = user_log.repository_name
685 else:
682 else:
686 repo_name = user_log.repository.repo_name
683 repo_name = user_log.repository.repo_name
687 return link_to(_('Pull request #%s') % pull_request_id,
684 return link_to(_('Pull request #%s') % pull_request_id,
688 url('pullrequest_show', repo_name=repo_name,
685 url('pullrequest_show', repo_name=repo_name,
689 pull_request_id=pull_request_id))
686 pull_request_id=pull_request_id))
690
687
691 # action : translated str, callback(extractor), icon
688 # action : translated str, callback(extractor), icon
692 action_map = {
689 action_map = {
693 'user_deleted_repo': (_('[deleted] repository'),
690 'user_deleted_repo': (_('[deleted] repository'),
694 None, 'database_delete.png'),
691 None, 'database_delete.png'),
695 'user_created_repo': (_('[created] repository'),
692 'user_created_repo': (_('[created] repository'),
696 None, 'database_add.png'),
693 None, 'database_add.png'),
697 'user_created_fork': (_('[created] repository as fork'),
694 'user_created_fork': (_('[created] repository as fork'),
698 None, 'arrow_divide.png'),
695 None, 'arrow_divide.png'),
699 'user_forked_repo': (_('[forked] repository'),
696 'user_forked_repo': (_('[forked] repository'),
700 get_fork_name, 'arrow_divide.png'),
697 get_fork_name, 'arrow_divide.png'),
701 'user_updated_repo': (_('[updated] repository'),
698 'user_updated_repo': (_('[updated] repository'),
702 None, 'database_edit.png'),
699 None, 'database_edit.png'),
703 'admin_deleted_repo': (_('[delete] repository'),
700 'admin_deleted_repo': (_('[delete] repository'),
704 None, 'database_delete.png'),
701 None, 'database_delete.png'),
705 'admin_created_repo': (_('[created] repository'),
702 'admin_created_repo': (_('[created] repository'),
706 None, 'database_add.png'),
703 None, 'database_add.png'),
707 'admin_forked_repo': (_('[forked] repository'),
704 'admin_forked_repo': (_('[forked] repository'),
708 None, 'arrow_divide.png'),
705 None, 'arrow_divide.png'),
709 'admin_updated_repo': (_('[updated] repository'),
706 'admin_updated_repo': (_('[updated] repository'),
710 None, 'database_edit.png'),
707 None, 'database_edit.png'),
711 'admin_created_user': (_('[created] user'),
708 'admin_created_user': (_('[created] user'),
712 get_user_name, 'user_add.png'),
709 get_user_name, 'user_add.png'),
713 'admin_updated_user': (_('[updated] user'),
710 'admin_updated_user': (_('[updated] user'),
714 get_user_name, 'user_edit.png'),
711 get_user_name, 'user_edit.png'),
715 'admin_created_users_group': (_('[created] user group'),
712 'admin_created_users_group': (_('[created] user group'),
716 get_users_group, 'group_add.png'),
713 get_users_group, 'group_add.png'),
717 'admin_updated_users_group': (_('[updated] user group'),
714 'admin_updated_users_group': (_('[updated] user group'),
718 get_users_group, 'group_edit.png'),
715 get_users_group, 'group_edit.png'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
716 'user_commented_revision': (_('[commented] on revision in repository'),
720 get_cs_links, 'comment_add.png'),
717 get_cs_links, 'comment_add.png'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
718 'user_commented_pull_request': (_('[commented] on pull request for'),
722 get_pull_request, 'comment_add.png'),
719 get_pull_request, 'comment_add.png'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
720 'user_closed_pull_request': (_('[closed] pull request for'),
724 get_pull_request, 'tick.png'),
721 get_pull_request, 'tick.png'),
725 'push': (_('[pushed] into'),
722 'push': (_('[pushed] into'),
726 get_cs_links, 'script_add.png'),
723 get_cs_links, 'script_add.png'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
724 'push_local': (_('[committed via RhodeCode] into repository'),
728 get_cs_links, 'script_edit.png'),
725 get_cs_links, 'script_edit.png'),
729 'push_remote': (_('[pulled from remote] into repository'),
726 'push_remote': (_('[pulled from remote] into repository'),
730 get_cs_links, 'connect.png'),
727 get_cs_links, 'connect.png'),
731 'pull': (_('[pulled] from'),
728 'pull': (_('[pulled] from'),
732 None, 'down_16.png'),
729 None, 'down_16.png'),
733 'started_following_repo': (_('[started following] repository'),
730 'started_following_repo': (_('[started following] repository'),
734 None, 'heart_add.png'),
731 None, 'heart_add.png'),
735 'stopped_following_repo': (_('[stopped following] repository'),
732 'stopped_following_repo': (_('[stopped following] repository'),
736 None, 'heart_delete.png'),
733 None, 'heart_delete.png'),
737 }
734 }
738
735
739 action_str = action_map.get(action, action)
736 action_str = action_map.get(action, action)
740 if feed:
737 if feed:
741 action = action_str[0].replace('[', '').replace(']', '')
738 action = action_str[0].replace('[', '').replace(']', '')
742 else:
739 else:
743 action = action_str[0]\
740 action = action_str[0]\
744 .replace('[', '<span class="journal_highlight">')\
741 .replace('[', '<span class="journal_highlight">')\
745 .replace(']', '</span>')
742 .replace(']', '</span>')
746
743
747 action_params_func = lambda: ""
744 action_params_func = lambda: ""
748
745
749 if callable(action_str[1]):
746 if callable(action_str[1]):
750 action_params_func = action_str[1]
747 action_params_func = action_str[1]
751
748
752 def action_parser_icon():
749 def action_parser_icon():
753 action = user_log.action
750 action = user_log.action
754 action_params = None
751 action_params = None
755 x = action.split(':')
752 x = action.split(':')
756
753
757 if len(x) > 1:
754 if len(x) > 1:
758 action, action_params = x
755 action, action_params = x
759
756
760 tmpl = """<img src="%s%s" alt="%s"/>"""
757 tmpl = """<img src="%s%s" alt="%s"/>"""
761 ico = action_map.get(action, ['', '', ''])[2]
758 ico = action_map.get(action, ['', '', ''])[2]
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
759 return literal(tmpl % ((url('/images/icons/')), ico, action))
763
760
764 # returned callbacks we need to call to get
761 # returned callbacks we need to call to get
765 return [lambda: literal(action), action_params_func, action_parser_icon]
762 return [lambda: literal(action), action_params_func, action_parser_icon]
766
763
767
764
768
765
769 #==============================================================================
766 #==============================================================================
770 # PERMS
767 # PERMS
771 #==============================================================================
768 #==============================================================================
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
769 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
770 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
774 HasReposGroupPermissionAny
771 HasReposGroupPermissionAny
775
772
776
773
777 #==============================================================================
774 #==============================================================================
778 # GRAVATAR URL
775 # GRAVATAR URL
779 #==============================================================================
776 #==============================================================================
780
777
781 def gravatar_url(email_address, size=30):
778 def gravatar_url(email_address, size=30):
782 from pylons import url # doh, we need to re-import url to mock it later
779 from pylons import url # doh, we need to re-import url to mock it later
783 _def = 'anonymous@rhodecode.org'
780 _def = 'anonymous@rhodecode.org'
784 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
781 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
785 email_address = email_address or _def
782 email_address = email_address or _def
786 if (not use_gravatar or not email_address or email_address == _def):
783 if (not use_gravatar or not email_address or email_address == _def):
787 f = lambda a, l: min(l, key=lambda x: abs(x - a))
784 f = lambda a, l: min(l, key=lambda x: abs(x - a))
788 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
785 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
789
786
790 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
787 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
791 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
788 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
792 parsed_url = urlparse.urlparse(url.current(qualified=True))
789 parsed_url = urlparse.urlparse(url.current(qualified=True))
793 tmpl = tmpl.replace('{email}', email_address)\
790 tmpl = tmpl.replace('{email}', email_address)\
794 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
791 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
795 .replace('{netloc}', parsed_url.netloc)\
792 .replace('{netloc}', parsed_url.netloc)\
796 .replace('{scheme}', parsed_url.scheme)\
793 .replace('{scheme}', parsed_url.scheme)\
797 .replace('{size}', str(size))
794 .replace('{size}', str(size))
798 return tmpl
795 return tmpl
799
796
800 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
797 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
801 default = 'identicon'
798 default = 'identicon'
802 baseurl_nossl = "http://www.gravatar.com/avatar/"
799 baseurl_nossl = "http://www.gravatar.com/avatar/"
803 baseurl_ssl = "https://secure.gravatar.com/avatar/"
800 baseurl_ssl = "https://secure.gravatar.com/avatar/"
804 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
801 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
805
802
806 if isinstance(email_address, unicode):
803 if isinstance(email_address, unicode):
807 #hashlib crashes on unicode items
804 #hashlib crashes on unicode items
808 email_address = safe_str(email_address)
805 email_address = safe_str(email_address)
809 # construct the url
806 # construct the url
810 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
807 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
811 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
808 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
812
809
813 return gravatar_url
810 return gravatar_url
814
811
815
812
816 #==============================================================================
813 #==============================================================================
817 # REPO PAGER, PAGER FOR REPOSITORY
814 # REPO PAGER, PAGER FOR REPOSITORY
818 #==============================================================================
815 #==============================================================================
819 class RepoPage(Page):
816 class RepoPage(Page):
820
817
821 def __init__(self, collection, page=1, items_per_page=20,
818 def __init__(self, collection, page=1, items_per_page=20,
822 item_count=None, url=None, **kwargs):
819 item_count=None, url=None, **kwargs):
823
820
824 """Create a "RepoPage" instance. special pager for paging
821 """Create a "RepoPage" instance. special pager for paging
825 repository
822 repository
826 """
823 """
827 self._url_generator = url
824 self._url_generator = url
828
825
829 # Safe the kwargs class-wide so they can be used in the pager() method
826 # Safe the kwargs class-wide so they can be used in the pager() method
830 self.kwargs = kwargs
827 self.kwargs = kwargs
831
828
832 # Save a reference to the collection
829 # Save a reference to the collection
833 self.original_collection = collection
830 self.original_collection = collection
834
831
835 self.collection = collection
832 self.collection = collection
836
833
837 # The self.page is the number of the current page.
834 # The self.page is the number of the current page.
838 # The first page has the number 1!
835 # The first page has the number 1!
839 try:
836 try:
840 self.page = int(page) # make it int() if we get it as a string
837 self.page = int(page) # make it int() if we get it as a string
841 except (ValueError, TypeError):
838 except (ValueError, TypeError):
842 self.page = 1
839 self.page = 1
843
840
844 self.items_per_page = items_per_page
841 self.items_per_page = items_per_page
845
842
846 # Unless the user tells us how many items the collections has
843 # Unless the user tells us how many items the collections has
847 # we calculate that ourselves.
844 # we calculate that ourselves.
848 if item_count is not None:
845 if item_count is not None:
849 self.item_count = item_count
846 self.item_count = item_count
850 else:
847 else:
851 self.item_count = len(self.collection)
848 self.item_count = len(self.collection)
852
849
853 # Compute the number of the first and last available page
850 # Compute the number of the first and last available page
854 if self.item_count > 0:
851 if self.item_count > 0:
855 self.first_page = 1
852 self.first_page = 1
856 self.page_count = int(math.ceil(float(self.item_count) /
853 self.page_count = int(math.ceil(float(self.item_count) /
857 self.items_per_page))
854 self.items_per_page))
858 self.last_page = self.first_page + self.page_count - 1
855 self.last_page = self.first_page + self.page_count - 1
859
856
860 # Make sure that the requested page number is the range of
857 # Make sure that the requested page number is the range of
861 # valid pages
858 # valid pages
862 if self.page > self.last_page:
859 if self.page > self.last_page:
863 self.page = self.last_page
860 self.page = self.last_page
864 elif self.page < self.first_page:
861 elif self.page < self.first_page:
865 self.page = self.first_page
862 self.page = self.first_page
866
863
867 # Note: the number of items on this page can be less than
864 # Note: the number of items on this page can be less than
868 # items_per_page if the last page is not full
865 # items_per_page if the last page is not full
869 self.first_item = max(0, (self.item_count) - (self.page *
866 self.first_item = max(0, (self.item_count) - (self.page *
870 items_per_page))
867 items_per_page))
871 self.last_item = ((self.item_count - 1) - items_per_page *
868 self.last_item = ((self.item_count - 1) - items_per_page *
872 (self.page - 1))
869 (self.page - 1))
873
870
874 self.items = list(self.collection[self.first_item:self.last_item + 1])
871 self.items = list(self.collection[self.first_item:self.last_item + 1])
875
872
876 # Links to previous and next page
873 # Links to previous and next page
877 if self.page > self.first_page:
874 if self.page > self.first_page:
878 self.previous_page = self.page - 1
875 self.previous_page = self.page - 1
879 else:
876 else:
880 self.previous_page = None
877 self.previous_page = None
881
878
882 if self.page < self.last_page:
879 if self.page < self.last_page:
883 self.next_page = self.page + 1
880 self.next_page = self.page + 1
884 else:
881 else:
885 self.next_page = None
882 self.next_page = None
886
883
887 # No items available
884 # No items available
888 else:
885 else:
889 self.first_page = None
886 self.first_page = None
890 self.page_count = 0
887 self.page_count = 0
891 self.last_page = None
888 self.last_page = None
892 self.first_item = None
889 self.first_item = None
893 self.last_item = None
890 self.last_item = None
894 self.previous_page = None
891 self.previous_page = None
895 self.next_page = None
892 self.next_page = None
896 self.items = []
893 self.items = []
897
894
898 # This is a subclass of the 'list' type. Initialise the list now.
895 # This is a subclass of the 'list' type. Initialise the list now.
899 list.__init__(self, reversed(self.items))
896 list.__init__(self, reversed(self.items))
900
897
901
898
902 def changed_tooltip(nodes):
899 def changed_tooltip(nodes):
903 """
900 """
904 Generates a html string for changed nodes in changeset page.
901 Generates a html string for changed nodes in changeset page.
905 It limits the output to 30 entries
902 It limits the output to 30 entries
906
903
907 :param nodes: LazyNodesGenerator
904 :param nodes: LazyNodesGenerator
908 """
905 """
909 if nodes:
906 if nodes:
910 pref = ': <br/> '
907 pref = ': <br/> '
911 suf = ''
908 suf = ''
912 if len(nodes) > 30:
909 if len(nodes) > 30:
913 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
910 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
914 return literal(pref + '<br/> '.join([safe_unicode(x.path)
911 return literal(pref + '<br/> '.join([safe_unicode(x.path)
915 for x in nodes[:30]]) + suf)
912 for x in nodes[:30]]) + suf)
916 else:
913 else:
917 return ': ' + _('No Files')
914 return ': ' + _('No Files')
918
915
919
916
920 def repo_link(groups_and_repos):
917 def repo_link(groups_and_repos):
921 """
918 """
922 Makes a breadcrumbs link to repo within a group
919 Makes a breadcrumbs link to repo within a group
923 joins &raquo; on each group to create a fancy link
920 joins &raquo; on each group to create a fancy link
924
921
925 ex::
922 ex::
926 group >> subgroup >> repo
923 group >> subgroup >> repo
927
924
928 :param groups_and_repos:
925 :param groups_and_repos:
929 :param last_url:
926 :param last_url:
930 """
927 """
931 groups, just_name, repo_name = groups_and_repos
928 groups, just_name, repo_name = groups_and_repos
932 last_url = url('summary_home', repo_name=repo_name)
929 last_url = url('summary_home', repo_name=repo_name)
933 last_link = link_to(just_name, last_url)
930 last_link = link_to(just_name, last_url)
934
931
935 def make_link(group):
932 def make_link(group):
936 return link_to(group.name,
933 return link_to(group.name,
937 url('repos_group_home', group_name=group.group_name))
934 url('repos_group_home', group_name=group.group_name))
938 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
935 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
939
936
940
937
941 def fancy_file_stats(stats):
938 def fancy_file_stats(stats):
942 """
939 """
943 Displays a fancy two colored bar for number of added/deleted
940 Displays a fancy two colored bar for number of added/deleted
944 lines of code on file
941 lines of code on file
945
942
946 :param stats: two element list of added/deleted lines of code
943 :param stats: two element list of added/deleted lines of code
947 """
944 """
948 def cgen(l_type, a_v, d_v):
945 def cgen(l_type, a_v, d_v):
949 mapping = {'tr': 'top-right-rounded-corner-mid',
946 mapping = {'tr': 'top-right-rounded-corner-mid',
950 'tl': 'top-left-rounded-corner-mid',
947 'tl': 'top-left-rounded-corner-mid',
951 'br': 'bottom-right-rounded-corner-mid',
948 'br': 'bottom-right-rounded-corner-mid',
952 'bl': 'bottom-left-rounded-corner-mid'}
949 'bl': 'bottom-left-rounded-corner-mid'}
953 map_getter = lambda x: mapping[x]
950 map_getter = lambda x: mapping[x]
954
951
955 if l_type == 'a' and d_v:
952 if l_type == 'a' and d_v:
956 #case when added and deleted are present
953 #case when added and deleted are present
957 return ' '.join(map(map_getter, ['tl', 'bl']))
954 return ' '.join(map(map_getter, ['tl', 'bl']))
958
955
959 if l_type == 'a' and not d_v:
956 if l_type == 'a' and not d_v:
960 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
957 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
961
958
962 if l_type == 'd' and a_v:
959 if l_type == 'd' and a_v:
963 return ' '.join(map(map_getter, ['tr', 'br']))
960 return ' '.join(map(map_getter, ['tr', 'br']))
964
961
965 if l_type == 'd' and not a_v:
962 if l_type == 'd' and not a_v:
966 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
963 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
967
964
968 a, d = stats[0], stats[1]
965 a, d = stats[0], stats[1]
969 width = 100
966 width = 100
970
967
971 if a == 'b':
968 if a == 'b':
972 #binary mode
969 #binary mode
973 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
970 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
974 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
971 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
975 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
972 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
976
973
977 t = stats[0] + stats[1]
974 t = stats[0] + stats[1]
978 unit = float(width) / (t or 1)
975 unit = float(width) / (t or 1)
979
976
980 # needs > 9% of width to be visible or 0 to be hidden
977 # needs > 9% of width to be visible or 0 to be hidden
981 a_p = max(9, unit * a) if a > 0 else 0
978 a_p = max(9, unit * a) if a > 0 else 0
982 d_p = max(9, unit * d) if d > 0 else 0
979 d_p = max(9, unit * d) if d > 0 else 0
983 p_sum = a_p + d_p
980 p_sum = a_p + d_p
984
981
985 if p_sum > width:
982 if p_sum > width:
986 #adjust the percentage to be == 100% since we adjusted to 9
983 #adjust the percentage to be == 100% since we adjusted to 9
987 if a_p > d_p:
984 if a_p > d_p:
988 a_p = a_p - (p_sum - width)
985 a_p = a_p - (p_sum - width)
989 else:
986 else:
990 d_p = d_p - (p_sum - width)
987 d_p = d_p - (p_sum - width)
991
988
992 a_v = a if a > 0 else ''
989 a_v = a if a > 0 else ''
993 d_v = d if d > 0 else ''
990 d_v = d if d > 0 else ''
994
991
995 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
992 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
996 cgen('a', a_v, d_v), a_p, a_v
993 cgen('a', a_v, d_v), a_p, a_v
997 )
994 )
998 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
995 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
999 cgen('d', a_v, d_v), d_p, d_v
996 cgen('d', a_v, d_v), d_p, d_v
1000 )
997 )
1001 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
998 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1002
999
1003
1000
1004 def urlify_text(text_, safe=True):
1001 def urlify_text(text_, safe=True):
1005 """
1002 """
1006 Extrac urls from text and make html links out of them
1003 Extrac urls from text and make html links out of them
1007
1004
1008 :param text_:
1005 :param text_:
1009 """
1006 """
1010
1007
1011 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1008 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1012 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1009 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1013
1010
1014 def url_func(match_obj):
1011 def url_func(match_obj):
1015 url_full = match_obj.groups()[0]
1012 url_full = match_obj.groups()[0]
1016 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1013 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1017 _newtext = url_pat.sub(url_func, text_)
1014 _newtext = url_pat.sub(url_func, text_)
1018 if safe:
1015 if safe:
1019 return literal(_newtext)
1016 return literal(_newtext)
1020 return _newtext
1017 return _newtext
1021
1018
1022
1019
1023 def urlify_changesets(text_, repository):
1020 def urlify_changesets(text_, repository):
1024 """
1021 """
1025 Extract revision ids from changeset and make link from them
1022 Extract revision ids from changeset and make link from them
1026
1023
1027 :param text_:
1024 :param text_:
1028 :param repository: repo name to build the URL with
1025 :param repository: repo name to build the URL with
1029 """
1026 """
1030 from pylons import url # doh, we need to re-import url to mock it later
1027 from pylons import url # doh, we need to re-import url to mock it later
1031 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1028 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1032
1029
1033 def url_func(match_obj):
1030 def url_func(match_obj):
1034 rev = match_obj.groups()[1]
1031 rev = match_obj.groups()[1]
1035 pref = match_obj.groups()[0]
1032 pref = match_obj.groups()[0]
1036 suf = match_obj.groups()[2]
1033 suf = match_obj.groups()[2]
1037
1034
1038 tmpl = (
1035 tmpl = (
1039 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1036 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1040 '%(rev)s</a>%(suf)s'
1037 '%(rev)s</a>%(suf)s'
1041 )
1038 )
1042 return tmpl % {
1039 return tmpl % {
1043 'pref': pref,
1040 'pref': pref,
1044 'cls': 'revision-link',
1041 'cls': 'revision-link',
1045 'url': url('changeset_home', repo_name=repository, revision=rev),
1042 'url': url('changeset_home', repo_name=repository, revision=rev),
1046 'rev': rev,
1043 'rev': rev,
1047 'suf': suf
1044 'suf': suf
1048 }
1045 }
1049
1046
1050 newtext = URL_PAT.sub(url_func, text_)
1047 newtext = URL_PAT.sub(url_func, text_)
1051
1048
1052 return newtext
1049 return newtext
1053
1050
1054
1051
1055 def urlify_commit(text_, repository=None, link_=None):
1052 def urlify_commit(text_, repository=None, link_=None):
1056 """
1053 """
1057 Parses given text message and makes proper links.
1054 Parses given text message and makes proper links.
1058 issues are linked to given issue-server, and rest is a changeset link
1055 issues are linked to given issue-server, and rest is a changeset link
1059 if link_ is given, in other case it's a plain text
1056 if link_ is given, in other case it's a plain text
1060
1057
1061 :param text_:
1058 :param text_:
1062 :param repository:
1059 :param repository:
1063 :param link_: changeset link
1060 :param link_: changeset link
1064 """
1061 """
1065 import traceback
1062 import traceback
1066 from pylons import url # doh, we need to re-import url to mock it later
1063 from pylons import url # doh, we need to re-import url to mock it later
1067
1064
1068 def escaper(string):
1065 def escaper(string):
1069 return string.replace('<', '&lt;').replace('>', '&gt;')
1066 return string.replace('<', '&lt;').replace('>', '&gt;')
1070
1067
1071 def linkify_others(t, l):
1068 def linkify_others(t, l):
1072 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1069 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1073 links = []
1070 links = []
1074 for e in urls.split(t):
1071 for e in urls.split(t):
1075 if not urls.match(e):
1072 if not urls.match(e):
1076 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1073 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1077 else:
1074 else:
1078 links.append(e)
1075 links.append(e)
1079
1076
1080 return ''.join(links)
1077 return ''.join(links)
1081
1078
1082 # urlify changesets - extrac revisions and make link out of them
1079 # urlify changesets - extrac revisions and make link out of them
1083 newtext = urlify_changesets(escaper(text_), repository)
1080 newtext = urlify_changesets(escaper(text_), repository)
1084
1081
1085 # extract http/https links and make them real urls
1082 # extract http/https links and make them real urls
1086 newtext = urlify_text(newtext, safe=False)
1083 newtext = urlify_text(newtext, safe=False)
1087
1084
1088 try:
1085 try:
1089 from rhodecode import CONFIG
1086 from rhodecode import CONFIG
1090 conf = CONFIG
1087 conf = CONFIG
1091
1088
1092 # allow multiple issue servers to be used
1089 # allow multiple issue servers to be used
1093 valid_indices = [
1090 valid_indices = [
1094 x.group(1)
1091 x.group(1)
1095 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1092 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1096 if x and 'issue_server_link%s' % x.group(1) in conf
1093 if x and 'issue_server_link%s' % x.group(1) in conf
1097 and 'issue_prefix%s' % x.group(1) in conf
1094 and 'issue_prefix%s' % x.group(1) in conf
1098 ]
1095 ]
1099
1096
1100 log.debug('found issue server suffixes `%s` during valuation of: %s'
1097 log.debug('found issue server suffixes `%s` during valuation of: %s'
1101 % (','.join(valid_indices), newtext))
1098 % (','.join(valid_indices), newtext))
1102
1099
1103 for pattern_index in valid_indices:
1100 for pattern_index in valid_indices:
1104 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1101 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1105 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1102 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1106 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1103 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1107
1104
1108 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1105 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1109 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1106 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1110 ISSUE_PREFIX))
1107 ISSUE_PREFIX))
1111
1108
1112 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1109 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1113
1110
1114 def url_func(match_obj):
1111 def url_func(match_obj):
1115 pref = ''
1112 pref = ''
1116 if match_obj.group().startswith(' '):
1113 if match_obj.group().startswith(' '):
1117 pref = ' '
1114 pref = ' '
1118
1115
1119 issue_id = ''.join(match_obj.groups())
1116 issue_id = ''.join(match_obj.groups())
1120 tmpl = (
1117 tmpl = (
1121 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1118 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1122 '%(issue-prefix)s%(id-repr)s'
1119 '%(issue-prefix)s%(id-repr)s'
1123 '</a>'
1120 '</a>'
1124 )
1121 )
1125 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1122 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1126 if repository:
1123 if repository:
1127 url = url.replace('{repo}', repository)
1124 url = url.replace('{repo}', repository)
1128 repo_name = repository.split(URL_SEP)[-1]
1125 repo_name = repository.split(URL_SEP)[-1]
1129 url = url.replace('{repo_name}', repo_name)
1126 url = url.replace('{repo_name}', repo_name)
1130
1127
1131 return tmpl % {
1128 return tmpl % {
1132 'pref': pref,
1129 'pref': pref,
1133 'cls': 'issue-tracker-link',
1130 'cls': 'issue-tracker-link',
1134 'url': url,
1131 'url': url,
1135 'id-repr': issue_id,
1132 'id-repr': issue_id,
1136 'issue-prefix': ISSUE_PREFIX,
1133 'issue-prefix': ISSUE_PREFIX,
1137 'serv': ISSUE_SERVER_LNK,
1134 'serv': ISSUE_SERVER_LNK,
1138 }
1135 }
1139 newtext = URL_PAT.sub(url_func, newtext)
1136 newtext = URL_PAT.sub(url_func, newtext)
1140 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1137 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1141
1138
1142 # if we actually did something above
1139 # if we actually did something above
1143 if link_:
1140 if link_:
1144 # wrap not links into final link => link_
1141 # wrap not links into final link => link_
1145 newtext = linkify_others(newtext, link_)
1142 newtext = linkify_others(newtext, link_)
1146 except:
1143 except:
1147 log.error(traceback.format_exc())
1144 log.error(traceback.format_exc())
1148 pass
1145 pass
1149
1146
1150 return literal(newtext)
1147 return literal(newtext)
1151
1148
1152
1149
1153 def rst(source):
1150 def rst(source):
1154 return literal('<div class="rst-block">%s</div>' %
1151 return literal('<div class="rst-block">%s</div>' %
1155 MarkupRenderer.rst(source))
1152 MarkupRenderer.rst(source))
1156
1153
1157
1154
1158 def rst_w_mentions(source):
1155 def rst_w_mentions(source):
1159 """
1156 """
1160 Wrapped rst renderer with @mention highlighting
1157 Wrapped rst renderer with @mention highlighting
1161
1158
1162 :param source:
1159 :param source:
1163 """
1160 """
1164 return literal('<div class="rst-block">%s</div>' %
1161 return literal('<div class="rst-block">%s</div>' %
1165 MarkupRenderer.rst_with_mentions(source))
1162 MarkupRenderer.rst_with_mentions(source))
1166
1163
1167
1164
1168 def changeset_status(repo, revision):
1165 def changeset_status(repo, revision):
1169 return ChangesetStatusModel().get_status(repo, revision)
1166 return ChangesetStatusModel().get_status(repo, revision)
1170
1167
1171
1168
1172 def changeset_status_lbl(changeset_status):
1169 def changeset_status_lbl(changeset_status):
1173 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1170 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1174
1171
1175
1172
1176 def get_permission_name(key):
1173 def get_permission_name(key):
1177 return dict(Permission.PERMS).get(key)
1174 return dict(Permission.PERMS).get(key)
1178
1175
1179
1176
1180 def journal_filter_help():
1177 def journal_filter_help():
1181 return _(textwrap.dedent('''
1178 return _(textwrap.dedent('''
1182 Example filter terms:
1179 Example filter terms:
1183 repository:vcs
1180 repository:vcs
1184 username:marcin
1181 username:marcin
1185 action:*push*
1182 action:*push*
1186 ip:127.0.0.1
1183 ip:127.0.0.1
1187 date:20120101
1184 date:20120101
1188 date:[20120101100000 TO 20120102]
1185 date:[20120101100000 TO 20120102]
1189
1186
1190 Generate wildcards using '*' character:
1187 Generate wildcards using '*' character:
1191 "repositroy:vcs*" - search everything starting with 'vcs'
1188 "repositroy:vcs*" - search everything starting with 'vcs'
1192 "repository:*vcs*" - search for repository containing 'vcs'
1189 "repository:*vcs*" - search for repository containing 'vcs'
1193
1190
1194 Optional AND / OR operators in queries
1191 Optional AND / OR operators in queries
1195 "repository:vcs OR repository:test"
1192 "repository:vcs OR repository:test"
1196 "username:test AND repository:test*"
1193 "username:test AND repository:test*"
1197 '''))
1194 '''))
1198
1195
1199
1196
1200 def not_mapped_error(repo_name):
1197 def not_mapped_error(repo_name):
1201 flash(_('%s repository is not mapped to db perhaps'
1198 flash(_('%s repository is not mapped to db perhaps'
1202 ' it was created or renamed from the filesystem'
1199 ' it was created or renamed from the filesystem'
1203 ' please run the application again'
1200 ' please run the application again'
1204 ' in order to rescan repositories') % repo_name, category='error')
1201 ' in order to rescan repositories') % repo_name, category='error')
1205
1202
1206
1203
1207 def ip_range(ip_addr):
1204 def ip_range(ip_addr):
1208 from rhodecode.model.db import UserIpMap
1205 from rhodecode.model.db import UserIpMap
1209 s, e = UserIpMap._get_ip_range(ip_addr)
1206 s, e = UserIpMap._get_ip_range(ip_addr)
1210 return '%s - %s' % (s, e)
1207 return '%s - %s' % (s, e)
@@ -1,215 +1,215 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Permissions administration')} &middot; ${c.rhodecode_name}
5 ${_('Permissions administration')} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('permissions')}
11 ${_('permissions')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box box-left">
19 <div class="box box-left">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 </div>
23 </div>
24 <h3>${_('Default permissions')}</h3>
24 <h3>${_('Default permissions')}</h3>
25 ${h.form(url('permission', id='default'),method='put')}
25 ${h.form(url('permission', id='default'),method='put')}
26 <div class="form">
26 <div class="form">
27 <!-- fields -->
27 <!-- fields -->
28 <div class="fields">
28 <div class="fields">
29 <div class="field">
29 <div class="field">
30 <div class="label label-checkbox">
30 <div class="label label-checkbox">
31 <label for="anonymous">${_('Anonymous access')}:</label>
31 <label for="anonymous">${_('Anonymous access')}:</label>
32 </div>
32 </div>
33 <div class="checkboxes">
33 <div class="checkboxes">
34 <div class="checkbox">
34 <div class="checkbox">
35 ${h.checkbox('anonymous',True)}
35 ${h.checkbox('anonymous',True)}
36 </div>
36 </div>
37 </div>
37 </div>
38 </div>
38 </div>
39 <div class="field">
39 <div class="field">
40 <div class="label">
40 <div class="label">
41 <label for="default_repo_perm">${_('Repository')}:</label>
41 <label for="default_repo_perm">${_('Repository')}:</label>
42 </div>
42 </div>
43 <div class="select">
43 <div class="select">
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
45
45
46 ${h.checkbox('overwrite_default_repo','true')}
46 ${h.checkbox('overwrite_default_repo','true')}
47 <label for="overwrite_default_repo">
47 <label for="overwrite_default_repo">
48 <span class="tooltip"
48 <span class="tooltip"
49 title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
49 title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
50 ${_('overwrite existing settings')}</span> </label>
50 ${_('overwrite existing settings')}</span> </label>
51 </div>
51 </div>
52 </div>
52 </div>
53 <div class="field">
53 <div class="field">
54 <div class="label">
54 <div class="label">
55 <label for="default_group_perm">${_('Repository group')}:</label>
55 <label for="default_group_perm">${_('Repository group')}:</label>
56 </div>
56 </div>
57 <div class="select">
57 <div class="select">
58 ${h.select('default_group_perm','',c.group_perms_choices)}
58 ${h.select('default_group_perm','',c.group_perms_choices)}
59 ${h.checkbox('overwrite_default_group','true')}
59 ${h.checkbox('overwrite_default_group','true')}
60 <label for="overwrite_default_group">
60 <label for="overwrite_default_group">
61 <span class="tooltip"
61 <span class="tooltip"
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
63 ${_('overwrite existing settings')}</span> </label>
63 ${_('overwrite existing settings')}</span> </label>
64
64
65 </div>
65 </div>
66 </div>
66 </div>
67 <div class="field">
67 <div class="field">
68 <div class="label">
68 <div class="label">
69 <label for="default_register">${_('Registration')}:</label>
69 <label for="default_register">${_('Registration')}:</label>
70 </div>
70 </div>
71 <div class="select">
71 <div class="select">
72 ${h.select('default_register','',c.register_choices)}
72 ${h.select('default_register','',c.register_choices)}
73 </div>
73 </div>
74 </div>
74 </div>
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="default_create">${_('Repository creation')}:</label>
77 <label for="default_create">${_('Repository creation')}:</label>
78 </div>
78 </div>
79 <div class="select">
79 <div class="select">
80 ${h.select('default_create','',c.create_choices)}
80 ${h.select('default_create','',c.create_choices)}
81 </div>
81 </div>
82 </div>
82 </div>
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label for="default_fork">${_('Repository forking')}:</label>
85 <label for="default_fork">${_('Repository forking')}:</label>
86 </div>
86 </div>
87 <div class="select">
87 <div class="select">
88 ${h.select('default_fork','',c.fork_choices)}
88 ${h.select('default_fork','',c.fork_choices)}
89 </div>
89 </div>
90 </div>
90 </div>
91 <div class="buttons">
91 <div class="buttons">
92 ${h.submit('save',_('Save'),class_="ui-btn large")}
92 ${h.submit('save',_('Save'),class_="ui-btn large")}
93 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
93 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
94 </div>
94 </div>
95 </div>
95 </div>
96 </div>
96 </div>
97 ${h.end_form()}
97 ${h.end_form()}
98 </div>
98 </div>
99
99
100 <div style="min-height:780px" class="box box-right">
100 <div style="min-height:780px" class="box box-right">
101 <!-- box / title -->
101 <!-- box / title -->
102 <div class="title">
102 <div class="title">
103 <h5>${_('Default User Permissions')}</h5>
103 <h5>${_('Default User Permissions')}</h5>
104 </div>
104 </div>
105
105
106 ## permissions overview
106 ## permissions overview
107 <div id="perms" class="table">
107 <div id="perms" class="table">
108 %for section in sorted(c.perm_user.permissions.keys()):
108 %for section in sorted(c.perm_user.permissions.keys()):
109 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
109 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
110 %if not c.perm_user.permissions[section]:
110 %if not c.perm_user.permissions[section]:
111 <span class="empty_data">${_('Nothing here yet')}</span>
111 <span class="empty_data">${_('Nothing here yet')}</span>
112 %else:
112 %else:
113 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
113 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
114 <table id="tbl_list_${section}">
114 <table id="tbl_list_${section}">
115 <thead>
115 <thead>
116 <tr>
116 <tr>
117 <th class="left">${_('Name')}</th>
117 <th class="left">${_('Name')}</th>
118 <th class="left">${_('Permission')}</th>
118 <th class="left">${_('Permission')}</th>
119 <th class="left">${_('Edit Permission')}</th>
119 <th class="left">${_('Edit Permission')}</th>
120 </thead>
120 </thead>
121 <tbody>
121 <tbody>
122 %for k in sorted(c.perm_user.permissions[section], key=lambda s: s.lower):
122 %for k in sorted(c.perm_user.permissions[section], key=lambda s: s.lower):
123 <%
123 <%
124 if section != 'global':
124 if section != 'global':
125 section_perm = c.perm_user.permissions[section].get(k)
125 section_perm = c.perm_user.permissions[section].get(k)
126 _perm = section_perm.split('.')[-1]
126 _perm = section_perm.split('.')[-1]
127 else:
127 else:
128 _perm = section_perm = None
128 _perm = section_perm = None
129 %>
129 %>
130 <tr>
130 <tr>
131 <td>
131 <td>
132 %if section == 'repositories':
132 %if section == 'repositories':
133 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
133 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
134 %elif section == 'repositories_groups':
134 %elif section == 'repositories_groups':
135 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
135 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
136 %else:
136 %else:
137 ${h.get_permission_name(k)}
137 ${h.get_permission_name(k)}
138 %endif
138 %endif
139 </td>
139 </td>
140 <td>
140 <td>
141 %if section == 'global':
141 %if section == 'global':
142 ${h.bool2icon(k.split('.')[-1] != 'none')}
142 ${h.boolicon(k.split('.')[-1] != 'none')}
143 %else:
143 %else:
144 <span class="perm_tag ${_perm}">${section_perm}</span>
144 <span class="perm_tag ${_perm}">${section_perm}</span>
145 %endif
145 %endif
146 </td>
146 </td>
147 <td>
147 <td>
148 %if section == 'repositories':
148 %if section == 'repositories':
149 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
149 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 %elif section == 'repositories_groups':
150 %elif section == 'repositories_groups':
151 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
151 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 %else:
152 %else:
153 --
153 --
154 %endif
154 %endif
155 </td>
155 </td>
156 </tr>
156 </tr>
157 %endfor
157 %endfor
158 </tbody>
158 </tbody>
159 </table>
159 </table>
160 </div>
160 </div>
161 %endif
161 %endif
162 %endfor
162 %endfor
163 </div>
163 </div>
164 </div>
164 </div>
165 <div class="box box-left" style="clear:left">
165 <div class="box box-left" style="clear:left">
166 <!-- box / title -->
166 <!-- box / title -->
167 <div class="title">
167 <div class="title">
168 <h5>${_('Allowed IP addresses')}</h5>
168 <h5>${_('Allowed IP addresses')}</h5>
169 </div>
169 </div>
170
170
171 <div class="ips_wrap">
171 <div class="ips_wrap">
172 <table class="noborder">
172 <table class="noborder">
173 %if c.user_ip_map:
173 %if c.user_ip_map:
174 %for ip in c.user_ip_map:
174 %for ip in c.user_ip_map:
175 <tr>
175 <tr>
176 <td><div class="ip">${ip.ip_addr}</div></td>
176 <td><div class="ip">${ip.ip_addr}</div></td>
177 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
177 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
178 <td>
178 <td>
179 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
179 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
180 ${h.hidden('del_ip',ip.ip_id)}
180 ${h.hidden('del_ip',ip.ip_id)}
181 ${h.hidden('default_user', 'True')}
181 ${h.hidden('default_user', 'True')}
182 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
182 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
183 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
183 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
184 ${h.end_form()}
184 ${h.end_form()}
185 </td>
185 </td>
186 </tr>
186 </tr>
187 %endfor
187 %endfor
188 %else:
188 %else:
189 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
189 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
190 %endif
190 %endif
191 </table>
191 </table>
192 </div>
192 </div>
193
193
194 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
194 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
195 <div class="form">
195 <div class="form">
196 <!-- fields -->
196 <!-- fields -->
197 <div class="fields">
197 <div class="fields">
198 <div class="field">
198 <div class="field">
199 <div class="label">
199 <div class="label">
200 <label for="new_ip">${_('New ip address')}:</label>
200 <label for="new_ip">${_('New ip address')}:</label>
201 </div>
201 </div>
202 <div class="input">
202 <div class="input">
203 ${h.hidden('default_user', 'True')}
203 ${h.hidden('default_user', 'True')}
204 ${h.text('new_ip', class_='medium')}
204 ${h.text('new_ip', class_='medium')}
205 </div>
205 </div>
206 </div>
206 </div>
207 <div class="buttons">
207 <div class="buttons">
208 ${h.submit('save',_('Add'),class_="ui-btn large")}
208 ${h.submit('save',_('Add'),class_="ui-btn large")}
209 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
209 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
210 </div>
210 </div>
211 </div>
211 </div>
212 </div>
212 </div>
213 ${h.end_form()}
213 ${h.end_form()}
214 </div>
214 </div>
215 </%def>
215 </%def>
@@ -1,378 +1,378 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.html"/>
5 <%inherit file="/base/base.html"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
8 ${_('Edit repository')} ${c.repo_info.repo_name} &middot; ${c.rhodecode_name}
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="breadcrumbs_links()">
12 ${_('Settings')}
12 ${_('Settings')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 ${self.context_bar('options')}
20 ${self.context_bar('options')}
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
26 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="repo_name">${_('Name')}:</label>
32 <label for="repo_name">${_('Name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('repo_name',class_="medium")}
35 ${h.text('repo_name',class_="medium")}
36 </div>
36 </div>
37 </div>
37 </div>
38 <div class="field">
38 <div class="field">
39 <div class="label">
39 <div class="label">
40 <label for="clone_uri">${_('Clone uri')}:</label>
40 <label for="clone_uri">${_('Clone uri')}:</label>
41 </div>
41 </div>
42 <div class="input">
42 <div class="input">
43 ${h.text('clone_uri',class_="medium")}
43 ${h.text('clone_uri',class_="medium")}
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
44 <span class="help-block">${_('Optional http[s] url from which repository should be cloned.')}</span>
45 </div>
45 </div>
46 </div>
46 </div>
47 <div class="field">
47 <div class="field">
48 <div class="label">
48 <div class="label">
49 <label for="repo_group">${_('Repository group')}:</label>
49 <label for="repo_group">${_('Repository group')}:</label>
50 </div>
50 </div>
51 <div class="input">
51 <div class="input">
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
52 ${h.select('repo_group','',c.repo_groups,class_="medium")}
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
53 <span class="help-block">${_('Optional select a group to put this repository into.')}</span>
54 </div>
54 </div>
55 </div>
55 </div>
56 <div class="field">
56 <div class="field">
57 <div class="label">
57 <div class="label">
58 <label for="repo_type">${_('Type')}:</label>
58 <label for="repo_type">${_('Type')}:</label>
59 </div>
59 </div>
60 <div class="input">
60 <div class="input">
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
61 ${h.select('repo_type','hg',c.backends,class_="medium")}
62 </div>
62 </div>
63 </div>
63 </div>
64 <div class="field">
64 <div class="field">
65 <div class="label">
65 <div class="label">
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
66 <label for="repo_landing_rev">${_('Landing revision')}:</label>
67 </div>
67 </div>
68 <div class="input">
68 <div class="input">
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
69 ${h.select('repo_landing_rev','',c.landing_revs,class_="medium")}
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
70 <span class="help-block">${_('Default revision for files page, downloads, whoosh and readme')}</span>
71 </div>
71 </div>
72 </div>
72 </div>
73 <div class="field">
73 <div class="field">
74 <div class="label label-textarea">
74 <div class="label label-textarea">
75 <label for="repo_description">${_('Description')}:</label>
75 <label for="repo_description">${_('Description')}:</label>
76 </div>
76 </div>
77 <div class="textarea text-area editor">
77 <div class="textarea text-area editor">
78 ${h.textarea('repo_description')}
78 ${h.textarea('repo_description')}
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
79 <span class="help-block">${_('Keep it short and to the point. Use a README file for longer descriptions.')}</span>
80 </div>
80 </div>
81 </div>
81 </div>
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label label-checkbox">
84 <div class="label label-checkbox">
85 <label for="repo_private">${_('Private repository')}:</label>
85 <label for="repo_private">${_('Private repository')}:</label>
86 </div>
86 </div>
87 <div class="checkboxes">
87 <div class="checkboxes">
88 ${h.checkbox('repo_private',value="True")}
88 ${h.checkbox('repo_private',value="True")}
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
89 <span class="help-block">${_('Private repositories are only visible to people explicitly added as collaborators.')}</span>
90 </div>
90 </div>
91 </div>
91 </div>
92 <div class="field">
92 <div class="field">
93 <div class="label label-checkbox">
93 <div class="label label-checkbox">
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
94 <label for="repo_enable_statistics">${_('Enable statistics')}:</label>
95 </div>
95 </div>
96 <div class="checkboxes">
96 <div class="checkboxes">
97 ${h.checkbox('repo_enable_statistics',value="True")}
97 ${h.checkbox('repo_enable_statistics',value="True")}
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
98 <span class="help-block">${_('Enable statistics window on summary page.')}</span>
99 </div>
99 </div>
100 </div>
100 </div>
101 <div class="field">
101 <div class="field">
102 <div class="label label-checkbox">
102 <div class="label label-checkbox">
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
103 <label for="repo_enable_downloads">${_('Enable downloads')}:</label>
104 </div>
104 </div>
105 <div class="checkboxes">
105 <div class="checkboxes">
106 ${h.checkbox('repo_enable_downloads',value="True")}
106 ${h.checkbox('repo_enable_downloads',value="True")}
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
107 <span class="help-block">${_('Enable download menu on summary page.')}</span>
108 </div>
108 </div>
109 </div>
109 </div>
110 <div class="field">
110 <div class="field">
111 <div class="label label-checkbox">
111 <div class="label label-checkbox">
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
112 <label for="repo_enable_locking">${_('Enable locking')}:</label>
113 </div>
113 </div>
114 <div class="checkboxes">
114 <div class="checkboxes">
115 ${h.checkbox('repo_enable_locking',value="True")}
115 ${h.checkbox('repo_enable_locking',value="True")}
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
116 <span class="help-block">${_('Enable lock-by-pulling on repository.')}</span>
117 </div>
117 </div>
118 </div>
118 </div>
119 <div class="field">
119 <div class="field">
120 <div class="label">
120 <div class="label">
121 <label for="user">${_('Owner')}:</label>
121 <label for="user">${_('Owner')}:</label>
122 </div>
122 </div>
123 <div class="input input-medium ac">
123 <div class="input input-medium ac">
124 <div class="perm_ac">
124 <div class="perm_ac">
125 ${h.text('user',class_='yui-ac-input')}
125 ${h.text('user',class_='yui-ac-input')}
126 <span class="help-block">${_('Change owner of this repository.')}</span>
126 <span class="help-block">${_('Change owner of this repository.')}</span>
127 <div id="owner_container"></div>
127 <div id="owner_container"></div>
128 </div>
128 </div>
129 </div>
129 </div>
130 </div>
130 </div>
131 %if c.visual.repository_fields:
131 %if c.visual.repository_fields:
132 ## EXTRA FIELDS
132 ## EXTRA FIELDS
133 %for field in c.repo_fields:
133 %for field in c.repo_fields:
134 <div class="field">
134 <div class="field">
135 <div class="label">
135 <div class="label">
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
136 <label for="${field.field_key_prefixed}">${field.field_label} (${field.field_key}):</label>
137 </div>
137 </div>
138 <div class="input input-medium">
138 <div class="input input-medium">
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
139 ${h.text(field.field_key_prefixed, field.field_value, class_='medium')}
140 %if field.field_desc:
140 %if field.field_desc:
141 <span class="help-block">${field.field_desc}</span>
141 <span class="help-block">${field.field_desc}</span>
142 %endif
142 %endif
143 </div>
143 </div>
144 </div>
144 </div>
145 %endfor
145 %endfor
146 %endif
146 %endif
147 <div class="field">
147 <div class="field">
148 <div class="label">
148 <div class="label">
149 <label for="input">${_('Permissions')}:</label>
149 <label for="input">${_('Permissions')}:</label>
150 </div>
150 </div>
151 <div class="input">
151 <div class="input">
152 <%include file="repo_edit_perms.html"/>
152 <%include file="repo_edit_perms.html"/>
153 </div>
153 </div>
154 </div>
154 </div>
155
155
156 <div class="buttons">
156 <div class="buttons">
157 ${h.submit('save',_('Save'),class_="ui-btn large")}
157 ${h.submit('save',_('Save'),class_="ui-btn large")}
158 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
158 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
159 </div>
159 </div>
160 </div>
160 </div>
161 </div>
161 </div>
162 ${h.end_form()}
162 ${h.end_form()}
163 </div>
163 </div>
164
164
165 <div class="box box-right">
165 <div class="box box-right">
166 <div class="title">
166 <div class="title">
167 <h5>${_('Advanced settings')}</h5>
167 <h5>${_('Advanced settings')}</h5>
168 </div>
168 </div>
169
169
170 <h3>${_('Statistics')}</h3>
170 <h3>${_('Statistics')}</h3>
171 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
171 ${h.form(url('repo_stats', repo_name=c.repo_info.repo_name),method='delete')}
172 <div class="form">
172 <div class="form">
173 <div class="fields">
173 <div class="fields">
174 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
174 ${h.submit('reset_stats_%s' % c.repo_info.repo_name,_('Reset current statistics'),class_="ui-btn",onclick="return confirm('"+_('Confirm to remove current statistics')+"');")}
175 <div class="field" style="border:none;color:#888">
175 <div class="field" style="border:none;color:#888">
176 <ul>
176 <ul>
177 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
177 <li>${_('Fetched to rev')}: ${c.stats_revision}/${c.repo_last_rev}</li>
178 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
178 <li>${_('Stats gathered')}: ${c.stats_percentage}%</li>
179 </ul>
179 </ul>
180 </div>
180 </div>
181 </div>
181 </div>
182 </div>
182 </div>
183 ${h.end_form()}
183 ${h.end_form()}
184
184
185 %if c.repo_info.clone_uri:
185 %if c.repo_info.clone_uri:
186 <h3>${_('Remote')}</h3>
186 <h3>${_('Remote')}</h3>
187 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
187 ${h.form(url('repo_pull', repo_name=c.repo_info.repo_name),method='put')}
188 <div class="form">
188 <div class="form">
189 <div class="fields">
189 <div class="fields">
190 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
190 ${h.submit('remote_pull_%s' % c.repo_info.repo_name,_('Pull changes from remote location'),class_="ui-btn",onclick="return confirm('"+_('Confirm to pull changes from remote side')+"');")}
191 <div class="field" style="border:none">
191 <div class="field" style="border:none">
192 <ul>
192 <ul>
193 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
193 <li><a href="${c.repo_info.clone_uri}">${c.repo_info.clone_uri}</a></li>
194 </ul>
194 </ul>
195 </div>
195 </div>
196 </div>
196 </div>
197 </div>
197 </div>
198 ${h.end_form()}
198 ${h.end_form()}
199 %endif
199 %endif
200
200
201 <h3>${_('Cache')}</h3>
201 <h3>${_('Cache')}</h3>
202 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
202 ${h.form(url('repo_cache', repo_name=c.repo_info.repo_name),method='delete')}
203 <div class="form">
203 <div class="form">
204 <div class="fields">
204 <div class="fields">
205 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
205 ${h.submit('reset_cache_%s' % c.repo_info.repo_name,_('Invalidate repository cache'),class_="ui-btn",onclick="return confirm('"+_('Confirm to invalidate repository cache')+"');")}
206 <div class="field" style="border:none;color:#888">
206 <div class="field" style="border:none;color:#888">
207 <ul>
207 <ul>
208 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
208 <li>${_('Manually invalidate cache for this repository. On first access repository will be cached again')}
209 </li>
209 </li>
210 </ul>
210 </ul>
211 </div>
211 </div>
212 <div class="field" style="border:none;">
212 <div class="field" style="border:none;">
213 ${_('List of cached values')}
213 ${_('List of cached values')}
214 <table>
214 <table>
215 <tr>
215 <tr>
216 <th>${_('Prefix')}</th>
216 <th>${_('Prefix')}</th>
217 <th>${_('Key')}</th>
217 <th>${_('Key')}</th>
218 <th>${_('Active')}</th>
218 <th>${_('Active')}</th>
219 </tr>
219 </tr>
220 %for cache in c.repo_info.cache_keys:
220 %for cache in c.repo_info.cache_keys:
221 <tr>
221 <tr>
222 <td>${cache.get_prefix() or '-'}</td>
222 <td>${cache.get_prefix() or '-'}</td>
223 <td>${cache.cache_key}</td>
223 <td>${cache.cache_key}</td>
224 <td>${h.bool2icon(cache.cache_active)}</td>
224 <td>${h.boolicon(cache.cache_active)}</td>
225 </tr>
225 </tr>
226 %endfor
226 %endfor
227 </table>
227 </table>
228 </div>
228 </div>
229 </div>
229 </div>
230 </div>
230 </div>
231 ${h.end_form()}
231 ${h.end_form()}
232
232
233 <h3>${_('Public journal')}</h3>
233 <h3>${_('Public journal')}</h3>
234 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
234 ${h.form(url('repo_public_journal', repo_name=c.repo_info.repo_name),method='put')}
235 <div class="form">
235 <div class="form">
236 ${h.hidden('auth_token',str(h.get_token()))}
236 ${h.hidden('auth_token',str(h.get_token()))}
237 <div class="field">
237 <div class="field">
238 %if c.in_public_journal:
238 %if c.in_public_journal:
239 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
239 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Remove from public journal'),class_="ui-btn")}
240 %else:
240 %else:
241 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
241 ${h.submit('set_public_%s' % c.repo_info.repo_name,_('Add to public journal'),class_="ui-btn")}
242 %endif
242 %endif
243 </div>
243 </div>
244 <div class="field" style="border:none;color:#888">
244 <div class="field" style="border:none;color:#888">
245 <ul>
245 <ul>
246 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
246 <li>${_('All actions made on this repository will be accessible to everyone in public journal')}
247 </li>
247 </li>
248 </ul>
248 </ul>
249 </div>
249 </div>
250 </div>
250 </div>
251 ${h.end_form()}
251 ${h.end_form()}
252
252
253 <h3>${_('Locking')}</h3>
253 <h3>${_('Locking')}</h3>
254 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
254 ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')}
255 <div class="form">
255 <div class="form">
256 <div class="fields">
256 <div class="fields">
257 %if c.repo_info.locked[0]:
257 %if c.repo_info.locked[0]:
258 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
258 ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")}
259 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
259 ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))}
260 %else:
260 %else:
261 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
261 ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")}
262 ${_('Repository is not locked')}
262 ${_('Repository is not locked')}
263 %endif
263 %endif
264 </div>
264 </div>
265 <div class="field" style="border:none;color:#888">
265 <div class="field" style="border:none;color:#888">
266 <ul>
266 <ul>
267 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
267 <li>${_('Force locking on repository. Works only when anonymous access is disabled')}
268 </li>
268 </li>
269 </ul>
269 </ul>
270 </div>
270 </div>
271 </div>
271 </div>
272 ${h.end_form()}
272 ${h.end_form()}
273
273
274 <h3>${_('Set as fork of')}</h3>
274 <h3>${_('Set as fork of')}</h3>
275 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
275 ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')}
276 <div class="form">
276 <div class="form">
277 <div class="fields">
277 <div class="fields">
278 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
278 ${h.select('id_fork_of','',c.repos_list,class_="medium")}
279 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
279 ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)}
280 </div>
280 </div>
281 <div class="field" style="border:none;color:#888">
281 <div class="field" style="border:none;color:#888">
282 <ul>
282 <ul>
283 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
283 <li>${_('''Manually set this repository as a fork of another from the list''')}</li>
284 </ul>
284 </ul>
285 </div>
285 </div>
286 </div>
286 </div>
287 ${h.end_form()}
287 ${h.end_form()}
288
288
289 <h3>${_('Delete')}</h3>
289 <h3>${_('Delete')}</h3>
290 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
290 ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
291 <div class="form">
291 <div class="form">
292 <div class="fields">
292 <div class="fields">
293 <div class="field" style="border:none;color:#888">
293 <div class="field" style="border:none;color:#888">
294 ## <div class="label">
294 ## <div class="label">
295 ## <label for="">${_('Remove repository')}:</label>
295 ## <label for="">${_('Remove repository')}:</label>
296 ## </div>
296 ## </div>
297 <div class="checkboxes">
297 <div class="checkboxes">
298 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
298 ${h.submit('remove_%s' % c.repo_info.repo_name,_('Remove this repository'),class_="ui-btn red",onclick="return confirm('"+_('Confirm to delete this repository')+"');")}
299 %if c.repo_info.forks.count():
299 %if c.repo_info.forks.count():
300 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
300 - ${ungettext('this repository has %s fork', 'this repository has %s forks', c.repo_info.forks.count()) % c.repo_info.forks.count()}
301 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
301 <input type="radio" name="forks" value="detach_forks" checked="checked"/> <label for="forks">${_('Detach forks')}</label>
302 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
302 <input type="radio" name="forks" value="delete_forks" /> <label for="forks">${_('Delete forks')}</label>
303 %endif
303 %endif
304 <ul>
304 <ul>
305 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
305 <li>${_('This repository will be renamed in a special way in order to be unaccesible for RhodeCode and VCS systems. If you need to fully delete it from file system please do it manually')}</li>
306 </ul>
306 </ul>
307 </div>
307 </div>
308 </div>
308 </div>
309 </div>
309 </div>
310 </div>
310 </div>
311 ${h.end_form()}
311 ${h.end_form()}
312 </div>
312 </div>
313
313
314 ##TODO: this should be controlled by the VISUAL setting
314 ##TODO: this should be controlled by the VISUAL setting
315 %if c.visual.repository_fields:
315 %if c.visual.repository_fields:
316 <div class="box box-left" style="clear:left">
316 <div class="box box-left" style="clear:left">
317 <!-- box / title -->
317 <!-- box / title -->
318 <div class="title">
318 <div class="title">
319 <h5>${_('Extra fields')}</h5>
319 <h5>${_('Extra fields')}</h5>
320 </div>
320 </div>
321
321
322 <div class="emails_wrap">
322 <div class="emails_wrap">
323 <table class="noborder">
323 <table class="noborder">
324 %for field in c.repo_fields:
324 %for field in c.repo_fields:
325 <tr>
325 <tr>
326 <td>${field.field_label} (${field.field_key})</td>
326 <td>${field.field_label} (${field.field_key})</td>
327 <td>${field.field_type}</td>
327 <td>${field.field_type}</td>
328 <td>
328 <td>
329 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
329 ${h.form(url('delete_repo_fields', repo_name=c.repo_info.repo_name, field_id=field.repo_field_id),method='delete')}
330 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
330 ${h.submit('remove_%s' % field.repo_field_id, _('delete'), id="remove_field_%s" % field.repo_field_id,
331 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
331 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this field: %s') % field.field_key+"');")}
332 ${h.end_form()}
332 ${h.end_form()}
333 </td>
333 </td>
334 </tr>
334 </tr>
335 %endfor
335 %endfor
336 </table>
336 </table>
337 </div>
337 </div>
338
338
339 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
339 ${h.form(url('create_repo_fields', repo_name=c.repo_info.repo_name),method='put')}
340 <div class="form">
340 <div class="form">
341 <!-- fields -->
341 <!-- fields -->
342 <div class="fields">
342 <div class="fields">
343 <div class="field">
343 <div class="field">
344 <div class="label">
344 <div class="label">
345 <label for="new_field_key">${_('New field key')}:</label>
345 <label for="new_field_key">${_('New field key')}:</label>
346 </div>
346 </div>
347 <div class="input">
347 <div class="input">
348 ${h.text('new_field_key', class_='small')}
348 ${h.text('new_field_key', class_='small')}
349 </div>
349 </div>
350 </div>
350 </div>
351 <div class="field">
351 <div class="field">
352 <div class="label">
352 <div class="label">
353 <label for="new_field_label">${_('New field label')}:</label>
353 <label for="new_field_label">${_('New field label')}:</label>
354 </div>
354 </div>
355 <div class="input">
355 <div class="input">
356 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
356 ${h.text('new_field_label', class_='small', placeholder=_('Enter short label'))}
357 </div>
357 </div>
358 </div>
358 </div>
359
359
360 <div class="field">
360 <div class="field">
361 <div class="label">
361 <div class="label">
362 <label for="new_field_desc">${_('New field description')}:</label>
362 <label for="new_field_desc">${_('New field description')}:</label>
363 </div>
363 </div>
364 <div class="input">
364 <div class="input">
365 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
365 ${h.text('new_field_desc', class_='small', placeholder=_('Enter description of a field'))}
366 </div>
366 </div>
367 </div>
367 </div>
368
368
369 <div class="buttons">
369 <div class="buttons">
370 ${h.submit('save',_('Add'),class_="ui-btn large")}
370 ${h.submit('save',_('Add'),class_="ui-btn large")}
371 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
371 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
372 </div>
372 </div>
373 </div>
373 </div>
374 </div>
374 </div>
375 ${h.end_form()}
375 ${h.end_form()}
376 </div>
376 </div>
377 %endif
377 %endif
378 </%def>
378 </%def>
@@ -1,343 +1,343 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit user')} ${c.user.username} &middot; ${c.rhodecode_name}
5 ${_('Edit user')} ${c.user.username} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.user.username}"
13 ${_('edit')} "${c.user.username}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 <div class="form">
28 <div class="form">
29 <div class="field">
29 <div class="field">
30 <div class="gravatar_box">
30 <div class="gravatar_box">
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 <p>
32 <p>
33 %if c.use_gravatar:
33 %if c.use_gravatar:
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 <br/>${_('Using')} ${c.user.email}
35 <br/>${_('Using')} ${c.user.email}
36 %else:
36 %else:
37 <br/>${c.user.email}
37 <br/>${c.user.email}
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label>${_('API key')}:</label> ${c.user.api_key}
43 <label>${_('API key')}:</label> ${c.user.api_key}
44 </div>
44 </div>
45 </div>
45 </div>
46 ##show current ip just if we show ourself
46 ##show current ip just if we show ourself
47 %if c.rhodecode_user.username == c.user.username:
47 %if c.rhodecode_user.username == c.user.username:
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
50 <label>${_('Current IP')}:</label> ${c.perm_user.ip_addr or "?"}
51 </div>
51 </div>
52 </div>
52 </div>
53 %endif
53 %endif
54 <div class="fields">
54 <div class="fields">
55 <div class="field">
55 <div class="field">
56 <div class="label">
56 <div class="label">
57 <label for="username">${_('Username')}:</label>
57 <label for="username">${_('Username')}:</label>
58 </div>
58 </div>
59 <div class="input">
59 <div class="input">
60 %if c.ldap_dn:
60 %if c.ldap_dn:
61 ${h.text('username',class_='medium disabled', readonly="readonly")}
61 ${h.text('username',class_='medium disabled', readonly="readonly")}
62 %else:
62 %else:
63 ${h.text('username',class_='medium')}
63 ${h.text('username',class_='medium')}
64 %endif:
64 %endif:
65 </div>
65 </div>
66 </div>
66 </div>
67
67
68 <div class="field">
68 <div class="field">
69 <div class="label">
69 <div class="label">
70 <label for="ldap_dn">${_('LDAP DN')}:</label>
70 <label for="ldap_dn">${_('LDAP DN')}:</label>
71 </div>
71 </div>
72 <div class="input">
72 <div class="input">
73 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
73 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
74 </div>
74 </div>
75 </div>
75 </div>
76
76
77 <div class="field">
77 <div class="field">
78 <div class="label">
78 <div class="label">
79 <label for="new_password">${_('New password')}:</label>
79 <label for="new_password">${_('New password')}:</label>
80 </div>
80 </div>
81 <div class="input">
81 <div class="input">
82 ${h.password('new_password',class_='medium',autocomplete="off")}
82 ${h.password('new_password',class_='medium',autocomplete="off")}
83 </div>
83 </div>
84 </div>
84 </div>
85
85
86 <div class="field">
86 <div class="field">
87 <div class="label">
87 <div class="label">
88 <label for="password_confirmation">${_('New password confirmation')}:</label>
88 <label for="password_confirmation">${_('New password confirmation')}:</label>
89 </div>
89 </div>
90 <div class="input">
90 <div class="input">
91 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
91 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="field">
95 <div class="field">
96 <div class="label">
96 <div class="label">
97 <label for="firstname">${_('First Name')}:</label>
97 <label for="firstname">${_('First Name')}:</label>
98 </div>
98 </div>
99 <div class="input">
99 <div class="input">
100 ${h.text('firstname',class_='medium')}
100 ${h.text('firstname',class_='medium')}
101 </div>
101 </div>
102 </div>
102 </div>
103
103
104 <div class="field">
104 <div class="field">
105 <div class="label">
105 <div class="label">
106 <label for="lastname">${_('Last Name')}:</label>
106 <label for="lastname">${_('Last Name')}:</label>
107 </div>
107 </div>
108 <div class="input">
108 <div class="input">
109 ${h.text('lastname',class_='medium')}
109 ${h.text('lastname',class_='medium')}
110 </div>
110 </div>
111 </div>
111 </div>
112
112
113 <div class="field">
113 <div class="field">
114 <div class="label">
114 <div class="label">
115 <label for="email">${_('Email')}:</label>
115 <label for="email">${_('Email')}:</label>
116 </div>
116 </div>
117 <div class="input">
117 <div class="input">
118 ${h.text('email',class_='medium')}
118 ${h.text('email',class_='medium')}
119 </div>
119 </div>
120 </div>
120 </div>
121
121
122 <div class="field">
122 <div class="field">
123 <div class="label label-checkbox">
123 <div class="label label-checkbox">
124 <label for="active">${_('Active')}:</label>
124 <label for="active">${_('Active')}:</label>
125 </div>
125 </div>
126 <div class="checkboxes">
126 <div class="checkboxes">
127 ${h.checkbox('active',value=True)}
127 ${h.checkbox('active',value=True)}
128 </div>
128 </div>
129 </div>
129 </div>
130
130
131 <div class="field">
131 <div class="field">
132 <div class="label label-checkbox">
132 <div class="label label-checkbox">
133 <label for="admin">${_('Admin')}:</label>
133 <label for="admin">${_('Admin')}:</label>
134 </div>
134 </div>
135 <div class="checkboxes">
135 <div class="checkboxes">
136 ${h.checkbox('admin',value=True)}
136 ${h.checkbox('admin',value=True)}
137 </div>
137 </div>
138 </div>
138 </div>
139 <div class="buttons">
139 <div class="buttons">
140 ${h.submit('save',_('Save'),class_="ui-btn large")}
140 ${h.submit('save',_('Save'),class_="ui-btn large")}
141 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
141 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
142 </div>
142 </div>
143 </div>
143 </div>
144 </div>
144 </div>
145 ${h.end_form()}
145 ${h.end_form()}
146 </div>
146 </div>
147 <div style="min-height:780px" class="box box-right">
147 <div style="min-height:780px" class="box box-right">
148 <!-- box / title -->
148 <!-- box / title -->
149 <div class="title">
149 <div class="title">
150 <h5>${_('Permissions')}</h5>
150 <h5>${_('Permissions')}</h5>
151 </div>
151 </div>
152 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
152 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
153 <div class="form">
153 <div class="form">
154 <!-- fields -->
154 <!-- fields -->
155 <div class="fields">
155 <div class="fields">
156 <div class="field">
156 <div class="field">
157 <div class="label label-checkbox">
157 <div class="label label-checkbox">
158 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
158 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
159 </div>
159 </div>
160 <div class="checkboxes">
160 <div class="checkboxes">
161 ${h.checkbox('inherit_default_permissions',value=True)}
161 ${h.checkbox('inherit_default_permissions',value=True)}
162 </div>
162 </div>
163 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
163 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
164 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
164 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
165 </div>
165 </div>
166 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
166 <div id="inherit_overlay" style="${'opacity:0.3' if c.user.inherit_default_permissions else ''}" >
167 <div class="field">
167 <div class="field">
168 <div class="label label-checkbox">
168 <div class="label label-checkbox">
169 <label for="create_repo_perm">${_('Create repositories')}:</label>
169 <label for="create_repo_perm">${_('Create repositories')}:</label>
170 </div>
170 </div>
171 <div class="checkboxes">
171 <div class="checkboxes">
172 ${h.checkbox('create_repo_perm',value=True)}
172 ${h.checkbox('create_repo_perm',value=True)}
173 </div>
173 </div>
174 </div>
174 </div>
175 <div class="field">
175 <div class="field">
176 <div class="label label-checkbox">
176 <div class="label label-checkbox">
177 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
177 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
178 </div>
178 </div>
179 <div class="checkboxes">
179 <div class="checkboxes">
180 ${h.checkbox('fork_repo_perm',value=True)}
180 ${h.checkbox('fork_repo_perm',value=True)}
181 </div>
181 </div>
182 </div>
182 </div>
183 </div>
183 </div>
184 <div class="buttons">
184 <div class="buttons">
185 ${h.submit('save',_('Save'),class_="ui-btn large")}
185 ${h.submit('save',_('Save'),class_="ui-btn large")}
186 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
186 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
187 </div>
187 </div>
188 </div>
188 </div>
189 </div>
189 </div>
190 ${h.end_form()}
190 ${h.end_form()}
191
191
192 ## permissions overview
192 ## permissions overview
193 <div id="perms" class="table">
193 <div id="perms" class="table">
194 %for section in sorted(c.perm_user.permissions.keys()):
194 %for section in sorted(c.perm_user.permissions.keys()):
195 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
195 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
196 %if not c.perm_user.permissions[section]:
196 %if not c.perm_user.permissions[section]:
197 <span class="empty_data">${_('Nothing here yet')}</span>
197 <span class="empty_data">${_('Nothing here yet')}</span>
198 %else:
198 %else:
199 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
199 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
200 <table id="tbl_list_${section}">
200 <table id="tbl_list_${section}">
201 <thead>
201 <thead>
202 <tr>
202 <tr>
203 <th class="left">${_('Name')}</th>
203 <th class="left">${_('Name')}</th>
204 <th class="left">${_('Permission')}</th>
204 <th class="left">${_('Permission')}</th>
205 <th class="left">${_('Edit Permission')}</th>
205 <th class="left">${_('Edit Permission')}</th>
206 </thead>
206 </thead>
207 <tbody>
207 <tbody>
208 %for k in sorted(c.perm_user.permissions[section], key=lambda s: s.lower):
208 %for k in sorted(c.perm_user.permissions[section], key=lambda s: s.lower):
209 <%
209 <%
210 if section != 'global':
210 if section != 'global':
211 section_perm = c.perm_user.permissions[section].get(k)
211 section_perm = c.perm_user.permissions[section].get(k)
212 _perm = section_perm.split('.')[-1]
212 _perm = section_perm.split('.')[-1]
213 else:
213 else:
214 _perm = section_perm = None
214 _perm = section_perm = None
215 %>
215 %>
216 <tr>
216 <tr>
217 <td>
217 <td>
218 %if section == 'repositories':
218 %if section == 'repositories':
219 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
219 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
220 %elif section == 'repositories_groups':
220 %elif section == 'repositories_groups':
221 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
221 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
222 %else:
222 %else:
223 ${h.get_permission_name(k)}
223 ${h.get_permission_name(k)}
224 %endif
224 %endif
225 </td>
225 </td>
226 <td>
226 <td>
227 %if section == 'global':
227 %if section == 'global':
228 ${h.bool2icon(k.split('.')[-1] != 'none')}
228 ${h.boolicon(k.split('.')[-1] != 'none')}
229 %else:
229 %else:
230 <span class="perm_tag ${_perm}">${section_perm}</span>
230 <span class="perm_tag ${_perm}">${section_perm}</span>
231 %endif
231 %endif
232 </td>
232 </td>
233 <td>
233 <td>
234 %if section == 'repositories':
234 %if section == 'repositories':
235 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
235 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
236 %elif section == 'repositories_groups':
236 %elif section == 'repositories_groups':
237 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
237 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
238 %else:
238 %else:
239 --
239 --
240 %endif
240 %endif
241 </td>
241 </td>
242 </tr>
242 </tr>
243 %endfor
243 %endfor
244 </tbody>
244 </tbody>
245 </table>
245 </table>
246 </div>
246 </div>
247 %endif
247 %endif
248 %endfor
248 %endfor
249 </div>
249 </div>
250 </div>
250 </div>
251 <div class="box box-left" style="clear:left">
251 <div class="box box-left" style="clear:left">
252 <!-- box / title -->
252 <!-- box / title -->
253 <div class="title">
253 <div class="title">
254 <h5>${_('Email addresses')}</h5>
254 <h5>${_('Email addresses')}</h5>
255 </div>
255 </div>
256
256
257 <div class="emails_wrap">
257 <div class="emails_wrap">
258 <table class="noborder">
258 <table class="noborder">
259 %for em in c.user_email_map:
259 %for em in c.user_email_map:
260 <tr>
260 <tr>
261 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
261 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
262 <td><div class="email">${em.email}</div></td>
262 <td><div class="email">${em.email}</div></td>
263 <td>
263 <td>
264 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
264 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
265 ${h.hidden('del_email',em.email_id)}
265 ${h.hidden('del_email',em.email_id)}
266 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
266 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
267 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
267 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
268 ${h.end_form()}
268 ${h.end_form()}
269 </td>
269 </td>
270 </tr>
270 </tr>
271 %endfor
271 %endfor
272 </table>
272 </table>
273 </div>
273 </div>
274
274
275 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
275 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
276 <div class="form">
276 <div class="form">
277 <!-- fields -->
277 <!-- fields -->
278 <div class="fields">
278 <div class="fields">
279 <div class="field">
279 <div class="field">
280 <div class="label">
280 <div class="label">
281 <label for="new_email">${_('New email address')}:</label>
281 <label for="new_email">${_('New email address')}:</label>
282 </div>
282 </div>
283 <div class="input">
283 <div class="input">
284 ${h.text('new_email', class_='medium')}
284 ${h.text('new_email', class_='medium')}
285 </div>
285 </div>
286 </div>
286 </div>
287 <div class="buttons">
287 <div class="buttons">
288 ${h.submit('save',_('Add'),class_="ui-btn large")}
288 ${h.submit('save',_('Add'),class_="ui-btn large")}
289 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
289 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
290 </div>
290 </div>
291 </div>
291 </div>
292 </div>
292 </div>
293 ${h.end_form()}
293 ${h.end_form()}
294 </div>
294 </div>
295 <div class="box box-left" style="clear:left">
295 <div class="box box-left" style="clear:left">
296 <!-- box / title -->
296 <!-- box / title -->
297 <div class="title">
297 <div class="title">
298 <h5>${_('Allowed IP addresses')}</h5>
298 <h5>${_('Allowed IP addresses')}</h5>
299 </div>
299 </div>
300
300
301 <div class="ips_wrap">
301 <div class="ips_wrap">
302 <table class="noborder">
302 <table class="noborder">
303 %if c.user_ip_map:
303 %if c.user_ip_map:
304 %for ip in c.user_ip_map:
304 %for ip in c.user_ip_map:
305 <tr>
305 <tr>
306 <td><div class="ip">${ip.ip_addr}</div></td>
306 <td><div class="ip">${ip.ip_addr}</div></td>
307 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
307 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
308 <td>
308 <td>
309 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
309 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
310 ${h.hidden('del_ip',ip.ip_id)}
310 ${h.hidden('del_ip',ip.ip_id)}
311 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
311 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
312 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
312 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
313 ${h.end_form()}
313 ${h.end_form()}
314 </td>
314 </td>
315 </tr>
315 </tr>
316 %endfor
316 %endfor
317 %else:
317 %else:
318 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
318 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
319 %endif
319 %endif
320 </table>
320 </table>
321 </div>
321 </div>
322
322
323 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
323 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
324 <div class="form">
324 <div class="form">
325 <!-- fields -->
325 <!-- fields -->
326 <div class="fields">
326 <div class="fields">
327 <div class="field">
327 <div class="field">
328 <div class="label">
328 <div class="label">
329 <label for="new_ip">${_('New ip address')}:</label>
329 <label for="new_ip">${_('New ip address')}:</label>
330 </div>
330 </div>
331 <div class="input">
331 <div class="input">
332 ${h.text('new_ip', class_='medium')}
332 ${h.text('new_ip', class_='medium')}
333 </div>
333 </div>
334 </div>
334 </div>
335 <div class="buttons">
335 <div class="buttons">
336 ${h.submit('save',_('Add'),class_="ui-btn large")}
336 ${h.submit('save',_('Add'),class_="ui-btn large")}
337 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
337 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
338 </div>
338 </div>
339 </div>
339 </div>
340 </div>
340 </div>
341 ${h.end_form()}
341 ${h.end_form()}
342 </div>
342 </div>
343 </%def>
343 </%def>
@@ -1,284 +1,284 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} &middot; ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 ${c.form|n}
24 ${c.form|n}
25 </div>
25 </div>
26
26
27 <div class="box box-right">
27 <div class="box box-right">
28 <!-- box / title -->
28 <!-- box / title -->
29 <div class="title">
29 <div class="title">
30 <h5>
30 <h5>
31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
31 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}" style="display: none"/>
32 </h5>
32 </h5>
33 <ul class="links" style="color:#DADADA">
33 <ul class="links" style="color:#DADADA">
34 <li>
34 <li>
35 <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
35 <span><a id="show_perms" class="link-white current" href="#perms">${_('My permissions')}</a> </span>
36 </li>
36 </li>
37 <li>
37 <li>
38 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
38 <span><a id="show_my" class="link-white" href="#my">${_('My repos')}</a> </span>
39 </li>
39 </li>
40 <li>
40 <li>
41 <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
41 <span><a id="show_pullrequests" class="link-white" href="#pullrequests">${_('My pull requests')}</a> </span>
42 </li>
42 </li>
43 </ul>
43 </ul>
44 </div>
44 </div>
45 <!-- end box / title -->
45 <!-- end box / title -->
46 <div id="perms_container">
46 <div id="perms_container">
47 <div id="perms" class="table">
47 <div id="perms" class="table">
48 %for section in sorted(c.rhodecode_user.permissions.keys()):
48 %for section in sorted(c.rhodecode_user.permissions.keys()):
49 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
49 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
50
50
51 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
51 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
52 <table id="tbl_list_${section}">
52 <table id="tbl_list_${section}">
53 <thead>
53 <thead>
54 <tr>
54 <tr>
55 <th class="left">${_('Name')}</th>
55 <th class="left">${_('Name')}</th>
56 <th class="left">${_('Permission')}</th>
56 <th class="left">${_('Permission')}</th>
57 </thead>
57 </thead>
58 <tbody>
58 <tbody>
59 %for k in sorted(c.rhodecode_user.permissions[section], key=lambda s: s.lower):
59 %for k in sorted(c.rhodecode_user.permissions[section], key=lambda s: s.lower):
60 <%
60 <%
61 if section != 'global':
61 if section != 'global':
62 section_perm = c.rhodecode_user.permissions[section].get(k)
62 section_perm = c.rhodecode_user.permissions[section].get(k)
63 _perm = section_perm.split('.')[-1]
63 _perm = section_perm.split('.')[-1]
64 else:
64 else:
65 _perm = section_perm = None
65 _perm = section_perm = None
66 %>
66 %>
67 %if _perm not in ['none']:
67 %if _perm not in ['none']:
68 <tr>
68 <tr>
69 <td>
69 <td>
70 %if section == 'repositories':
70 %if section == 'repositories':
71 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
71 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
72 %elif section == 'repositories_groups':
72 %elif section == 'repositories_groups':
73 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
73 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
74 %else:
74 %else:
75 ${k}
75 ${k}
76 %endif
76 %endif
77 </td>
77 </td>
78 <td>
78 <td>
79 %if section == 'global':
79 %if section == 'global':
80 ${h.bool2icon(True)}
80 ${h.boolicon(True)}
81 %else:
81 %else:
82 <span class="perm_tag ${_perm}">${section_perm}</span>
82 <span class="perm_tag ${_perm}">${section_perm}</span>
83 %endif
83 %endif
84 </td>
84 </td>
85 </tr>
85 </tr>
86 %endif
86 %endif
87 %endfor
87 %endfor
88 </tbody>
88 </tbody>
89 </table>
89 </table>
90 </div>
90 </div>
91 %endfor
91 %endfor
92 </div>
92 </div>
93 </div>
93 </div>
94 <div id="my_container" style="display:none">
94 <div id="my_container" style="display:none">
95 <div class="table yui-skin-sam" id="repos_list_wrap"></div>
95 <div class="table yui-skin-sam" id="repos_list_wrap"></div>
96 <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
96 <div id="user-paginator" style="padding: 0px 0px 0px 20px"></div>
97 </div>
97 </div>
98 <div id="pullrequests_container" class="table" style="display:none">
98 <div id="pullrequests_container" class="table" style="display:none">
99 ## loaded via AJAX
99 ## loaded via AJAX
100 ${_('Loading...')}
100 ${_('Loading...')}
101 </div>
101 </div>
102 </div>
102 </div>
103
103
104 <script type="text/javascript">
104 <script type="text/javascript">
105 pyroutes.register('admin_settings_my_pullrequests', "${url('admin_settings_my_pullrequests')}", []);
105 pyroutes.register('admin_settings_my_pullrequests', "${url('admin_settings_my_pullrequests')}", []);
106
106
107 var show_perms = function(e){
107 var show_perms = function(e){
108 YUD.addClass('show_perms', 'current');
108 YUD.addClass('show_perms', 'current');
109 YUD.removeClass('show_my','current');
109 YUD.removeClass('show_my','current');
110 YUD.removeClass('show_pullrequests','current');
110 YUD.removeClass('show_pullrequests','current');
111
111
112 YUD.setStyle('my_container','display','none');
112 YUD.setStyle('my_container','display','none');
113 YUD.setStyle('pullrequests_container','display','none');
113 YUD.setStyle('pullrequests_container','display','none');
114 YUD.setStyle('perms_container','display','');
114 YUD.setStyle('perms_container','display','');
115 YUD.setStyle('q_filter','display','none');
115 YUD.setStyle('q_filter','display','none');
116 }
116 }
117 YUE.on('show_perms','click',function(e){
117 YUE.on('show_perms','click',function(e){
118 show_perms();
118 show_perms();
119 })
119 })
120
120
121 var show_my = function(e){
121 var show_my = function(e){
122 YUD.addClass('show_my', 'current');
122 YUD.addClass('show_my', 'current');
123 YUD.removeClass('show_perms','current');
123 YUD.removeClass('show_perms','current');
124 YUD.removeClass('show_pullrequests','current');
124 YUD.removeClass('show_pullrequests','current');
125
125
126 YUD.setStyle('perms_container','display','none');
126 YUD.setStyle('perms_container','display','none');
127 YUD.setStyle('pullrequests_container','display','none');
127 YUD.setStyle('pullrequests_container','display','none');
128 YUD.setStyle('my_container','display','');
128 YUD.setStyle('my_container','display','');
129 YUD.setStyle('q_filter','display','');
129 YUD.setStyle('q_filter','display','');
130 if(!YUD.hasClass('show_my', 'loaded')){
130 if(!YUD.hasClass('show_my', 'loaded')){
131 table_renderer(${c.data |n});
131 table_renderer(${c.data |n});
132 YUD.addClass('show_my', 'loaded');
132 YUD.addClass('show_my', 'loaded');
133 }
133 }
134 }
134 }
135 YUE.on('show_my','click',function(e){
135 YUE.on('show_my','click',function(e){
136 show_my(e);
136 show_my(e);
137 })
137 })
138
138
139 var show_pullrequests = function(e){
139 var show_pullrequests = function(e){
140 YUD.addClass('show_pullrequests', 'current');
140 YUD.addClass('show_pullrequests', 'current');
141 YUD.removeClass('show_my','current');
141 YUD.removeClass('show_my','current');
142 YUD.removeClass('show_perms','current');
142 YUD.removeClass('show_perms','current');
143
143
144 YUD.setStyle('my_container','display','none');
144 YUD.setStyle('my_container','display','none');
145 YUD.setStyle('perms_container','display','none');
145 YUD.setStyle('perms_container','display','none');
146 YUD.setStyle('pullrequests_container','display','');
146 YUD.setStyle('pullrequests_container','display','');
147 YUD.setStyle('q_filter','display','none');
147 YUD.setStyle('q_filter','display','none');
148
148
149 var url = pyroutes.url('admin_settings_my_pullrequests');
149 var url = pyroutes.url('admin_settings_my_pullrequests');
150 if(YUD.get('show_closed') && YUD.get('show_closed').checked) {
150 if(YUD.get('show_closed') && YUD.get('show_closed').checked) {
151 var url = pyroutes.url('admin_settings_my_pullrequests', {'pr_show_closed': '1'});
151 var url = pyroutes.url('admin_settings_my_pullrequests', {'pr_show_closed': '1'});
152 }
152 }
153 ypjax(url, 'pullrequests_container', function(){
153 ypjax(url, 'pullrequests_container', function(){
154 YUE.on('show_closed','change',function (e) {
154 YUE.on('show_closed','change',function (e) {
155 show_pullrequests(e);
155 show_pullrequests(e);
156 });
156 });
157 });
157 });
158 }
158 }
159 YUE.on('show_pullrequests','click',function(e){
159 YUE.on('show_pullrequests','click',function(e){
160 show_pullrequests(e)
160 show_pullrequests(e)
161 })
161 })
162
162
163 var tabs = {
163 var tabs = {
164 'perms': show_perms,
164 'perms': show_perms,
165 'my': show_my,
165 'my': show_my,
166 'pullrequests': show_pullrequests
166 'pullrequests': show_pullrequests
167 }
167 }
168 var url = location.href.split('#');
168 var url = location.href.split('#');
169 if (url[1]) {
169 if (url[1]) {
170 //We have a hash
170 //We have a hash
171 var tabHash = url[1];
171 var tabHash = url[1];
172 var func = tabs[tabHash]
172 var func = tabs[tabHash]
173 if (func){
173 if (func){
174 func();
174 func();
175 }
175 }
176 }
176 }
177
177
178 function table_renderer(data){
178 function table_renderer(data){
179 var myDataSource = new YAHOO.util.DataSource(data);
179 var myDataSource = new YAHOO.util.DataSource(data);
180 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
180 myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
181
181
182 myDataSource.responseSchema = {
182 myDataSource.responseSchema = {
183 resultsList: "records",
183 resultsList: "records",
184 fields: [
184 fields: [
185 {key:"menu"},
185 {key:"menu"},
186 {key:"raw_name"},
186 {key:"raw_name"},
187 {key:"name"},
187 {key:"name"},
188 {key:"last_changeset"},
188 {key:"last_changeset"},
189 {key:"action"},
189 {key:"action"},
190 ]
190 ]
191 };
191 };
192 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
192 myDataSource.doBeforeCallback = function(req,raw,res,cb) {
193 // This is the filter function
193 // This is the filter function
194 var data = res.results || [],
194 var data = res.results || [],
195 filtered = [],
195 filtered = [],
196 i,l;
196 i,l;
197
197
198 if (req) {
198 if (req) {
199 req = req.toLowerCase();
199 req = req.toLowerCase();
200 for (i = 0; i<data.length; i++) {
200 for (i = 0; i<data.length; i++) {
201 var pos = data[i].raw_name.toLowerCase().indexOf(req)
201 var pos = data[i].raw_name.toLowerCase().indexOf(req)
202 if (pos != -1) {
202 if (pos != -1) {
203 filtered.push(data[i]);
203 filtered.push(data[i]);
204 }
204 }
205 }
205 }
206 res.results = filtered;
206 res.results = filtered;
207 }
207 }
208 return res;
208 return res;
209 }
209 }
210
210
211 // main table sorting
211 // main table sorting
212 var myColumnDefs = [
212 var myColumnDefs = [
213 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
213 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
214 {key:"name",label:"${_('Name')}",sortable:true,
214 {key:"name",label:"${_('Name')}",sortable:true,
215 sortOptions: { sortFunction: nameSort }},
215 sortOptions: { sortFunction: nameSort }},
216 {key:"last_changeset",label:"${_('Tip')}",sortable:true,
216 {key:"last_changeset",label:"${_('Tip')}",sortable:true,
217 sortOptions: { sortFunction: revisionSort }},
217 sortOptions: { sortFunction: revisionSort }},
218 {key:"action",label:"${_('Action')}",sortable:false},
218 {key:"action",label:"${_('Action')}",sortable:false},
219 ];
219 ];
220
220
221 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
221 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,{
222 sortedBy:{key:"name",dir:"asc"},
222 sortedBy:{key:"name",dir:"asc"},
223 paginator: new YAHOO.widget.Paginator({
223 paginator: new YAHOO.widget.Paginator({
224 rowsPerPage: 50,
224 rowsPerPage: 50,
225 alwaysVisible: false,
225 alwaysVisible: false,
226 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
226 template : "{PreviousPageLink} {FirstPageLink} {PageLinks} {LastPageLink} {NextPageLink}",
227 pageLinks: 5,
227 pageLinks: 5,
228 containerClass: 'pagination-wh',
228 containerClass: 'pagination-wh',
229 currentPageClass: 'pager_curpage',
229 currentPageClass: 'pager_curpage',
230 pageLinkClass: 'pager_link',
230 pageLinkClass: 'pager_link',
231 nextPageLinkLabel: '&gt;',
231 nextPageLinkLabel: '&gt;',
232 previousPageLinkLabel: '&lt;',
232 previousPageLinkLabel: '&lt;',
233 firstPageLinkLabel: '&lt;&lt;',
233 firstPageLinkLabel: '&lt;&lt;',
234 lastPageLinkLabel: '&gt;&gt;',
234 lastPageLinkLabel: '&gt;&gt;',
235 containers:['user-paginator']
235 containers:['user-paginator']
236 }),
236 }),
237
237
238 MSG_SORTASC:"${_('Click to sort ascending')}",
238 MSG_SORTASC:"${_('Click to sort ascending')}",
239 MSG_SORTDESC:"${_('Click to sort descending')}",
239 MSG_SORTDESC:"${_('Click to sort descending')}",
240 MSG_EMPTY:"${_('No records found.')}",
240 MSG_EMPTY:"${_('No records found.')}",
241 MSG_ERROR:"${_('Data error.')}",
241 MSG_ERROR:"${_('Data error.')}",
242 MSG_LOADING:"${_('Loading...')}",
242 MSG_LOADING:"${_('Loading...')}",
243 }
243 }
244 );
244 );
245 myDataTable.subscribe('postRenderEvent',function(oArgs) {
245 myDataTable.subscribe('postRenderEvent',function(oArgs) {
246 tooltip_activate();
246 tooltip_activate();
247 quick_repo_menu();
247 quick_repo_menu();
248 });
248 });
249
249
250 var filterTimeout = null;
250 var filterTimeout = null;
251
251
252 updateFilter = function() {
252 updateFilter = function() {
253 // Reset timeout
253 // Reset timeout
254 filterTimeout = null;
254 filterTimeout = null;
255
255
256 // Reset sort
256 // Reset sort
257 var state = myDataTable.getState();
257 var state = myDataTable.getState();
258 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
258 state.sortedBy = {key:'name', dir:YAHOO.widget.DataTable.CLASS_ASC};
259
259
260 // Get filtered data
260 // Get filtered data
261 myDataSource.sendRequest(YUD.get('q_filter').value,{
261 myDataSource.sendRequest(YUD.get('q_filter').value,{
262 success : myDataTable.onDataReturnInitializeTable,
262 success : myDataTable.onDataReturnInitializeTable,
263 failure : myDataTable.onDataReturnInitializeTable,
263 failure : myDataTable.onDataReturnInitializeTable,
264 scope : myDataTable,
264 scope : myDataTable,
265 argument: state
265 argument: state
266 });
266 });
267
267
268 };
268 };
269 YUE.on('q_filter','click',function(){
269 YUE.on('q_filter','click',function(){
270 if(!YUD.hasClass('q_filter', 'loaded')){
270 if(!YUD.hasClass('q_filter', 'loaded')){
271 YUD.get('q_filter').value = '';
271 YUD.get('q_filter').value = '';
272 //TODO: load here full list later to do search within groups
272 //TODO: load here full list later to do search within groups
273 YUD.addClass('q_filter', 'loaded');
273 YUD.addClass('q_filter', 'loaded');
274 }
274 }
275 });
275 });
276
276
277 YUE.on('q_filter','keyup',function (e) {
277 YUE.on('q_filter','keyup',function (e) {
278 clearTimeout(filterTimeout);
278 clearTimeout(filterTimeout);
279 filterTimeout = setTimeout(updateFilter,600);
279 filterTimeout = setTimeout(updateFilter,600);
280 });
280 });
281
281
282 }
282 }
283 </script>
283 </script>
284 </%def>
284 </%def>
@@ -1,55 +1,55 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('User groups administration')} &middot; ${c.rhodecode_name}
5 ${_('User groups administration')} &middot; ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${_('user groups')}
11 ${_('user groups')}
12 </%def>
12 </%def>
13
13
14 <%def name="page_nav()">
14 <%def name="page_nav()">
15 ${self.menu('admin')}
15 ${self.menu('admin')}
16 </%def>
16 </%def>
17
17
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 <ul class="links">
23 <ul class="links">
24 <li>
24 <li>
25 <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
25 <span>${h.link_to(_(u'Add new user group'),h.url('new_users_group'))}</span>
26 </li>
26 </li>
27
27
28 </ul>
28 </ul>
29 </div>
29 </div>
30 <!-- end box / title -->
30 <!-- end box / title -->
31 <div class="table">
31 <div class="table">
32 <table class="table_disp">
32 <table class="table_disp">
33 <tr class="header">
33 <tr class="header">
34 <th class="left">${_('group name')}</th>
34 <th class="left">${_('group name')}</th>
35 <th class="left">${_('members')}</th>
35 <th class="left">${_('members')}</th>
36 <th class="left">${_('active')}</th>
36 <th class="left">${_('active')}</th>
37 <th class="left">${_('action')}</th>
37 <th class="left">${_('action')}</th>
38 </tr>
38 </tr>
39 %for cnt,u_group in enumerate(c.users_groups_list):
39 %for cnt,u_group in enumerate(c.users_groups_list):
40 <tr class="parity${cnt%2}">
40 <tr class="parity${cnt%2}">
41 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
41 <td>${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))}</td>
42 <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
42 <td><span class="tooltip" title="${h.tooltip(', '.join(map(h.safe_unicode,[x.user.username for x in u_group.members[:50]])))}">${len(u_group.members)}</span></td>
43 <td>${h.bool2icon(u_group.users_group_active)}</td>
43 <td>${h.boolicon(u_group.users_group_active)}</td>
44 <td>
44 <td>
45 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
45 ${h.form(url('users_group', id=u_group.users_group_id),method='delete')}
46 ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
46 ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id,
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")}
47 class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user group: %s') % u_group.users_group_name+"');")}
48 ${h.end_form()}
48 ${h.end_form()}
49 </td>
49 </td>
50 </tr>
50 </tr>
51 %endfor
51 %endfor
52 </table>
52 </table>
53 </div>
53 </div>
54 </div>
54 </div>
55 </%def>
55 </%def>
General Comments 0
You need to be logged in to leave comments. Login now