##// END OF EJS Templates
consistent handling of grant/revoke of permissions widgets...
marcink -
r3715:25dbbdae beta
parent child Browse files
Show More
@@ -1,679 +1,669 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10
11 11 # prefix for non repository related links needs to be prefixed with `/`
12 12 ADMIN_PREFIX = '/_admin'
13 13
14 14
15 15 def make_map(config):
16 16 """Create, configure and return the routes Mapper"""
17 17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 18 always_scan=config['debug'])
19 19 rmap.minimization = False
20 20 rmap.explicit = False
21 21
22 22 from rhodecode.lib.utils import is_valid_repo
23 23 from rhodecode.lib.utils import is_valid_repos_group
24 24
25 25 def check_repo(environ, match_dict):
26 26 """
27 27 check for valid repository for proper 404 handling
28 28
29 29 :param environ:
30 30 :param match_dict:
31 31 """
32 32 from rhodecode.model.db import Repository
33 33 repo_name = match_dict.get('repo_name')
34 34
35 35 if match_dict.get('f_path'):
36 36 #fix for multiple initial slashes that causes errors
37 37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38 38
39 39 try:
40 40 by_id = repo_name.split('_')
41 41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 42 repo_name = Repository.get(by_id[1]).repo_name
43 43 match_dict['repo_name'] = repo_name
44 44 except Exception:
45 45 pass
46 46
47 47 return is_valid_repo(repo_name, config['base_path'])
48 48
49 49 def check_group(environ, match_dict):
50 50 """
51 51 check for valid repository group for proper 404 handling
52 52
53 53 :param environ:
54 54 :param match_dict:
55 55 """
56 56 repos_group_name = match_dict.get('group_name')
57 57 return is_valid_repos_group(repos_group_name, config['base_path'])
58 58
59 59 def check_group_skip_path(environ, match_dict):
60 60 """
61 61 check for valid repository group for proper 404 handling, but skips
62 62 verification of existing path
63 63
64 64 :param environ:
65 65 :param match_dict:
66 66 """
67 67 repos_group_name = match_dict.get('group_name')
68 68 return is_valid_repos_group(repos_group_name, config['base_path'],
69 69 skip_path_check=True)
70 70
71 71 def check_int(environ, match_dict):
72 72 return match_dict.get('id').isdigit()
73 73
74 74 # The ErrorController route (handles 404/500 error pages); it should
75 75 # likely stay at the top, ensuring it can always be resolved
76 76 rmap.connect('/error/{action}', controller='error')
77 77 rmap.connect('/error/{action}/{id}', controller='error')
78 78
79 79 #==========================================================================
80 80 # CUSTOM ROUTES HERE
81 81 #==========================================================================
82 82
83 83 #MAIN PAGE
84 84 rmap.connect('home', '/', controller='home', action='index')
85 85 rmap.connect('repo_switcher', '/repos', controller='home',
86 86 action='repo_switcher')
87 87 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
88 88 controller='home', action='branch_tag_switcher')
89 89 rmap.connect('bugtracker',
90 90 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
91 91 _static=True)
92 92 rmap.connect('rst_help',
93 93 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
94 94 _static=True)
95 95 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
96 96
97 97 #ADMIN REPOSITORY REST ROUTES
98 98 with rmap.submapper(path_prefix=ADMIN_PREFIX,
99 99 controller='admin/repos') as m:
100 100 m.connect("repos", "/repos",
101 101 action="create", conditions=dict(method=["POST"]))
102 102 m.connect("repos", "/repos",
103 103 action="index", conditions=dict(method=["GET"]))
104 104 m.connect("formatted_repos", "/repos.{format}",
105 105 action="index",
106 106 conditions=dict(method=["GET"]))
107 107 m.connect("new_repo", "/create_repository",
108 108 action="create_repository", conditions=dict(method=["GET"]))
109 109 m.connect("/repos/{repo_name:.*?}",
110 110 action="update", conditions=dict(method=["PUT"],
111 111 function=check_repo))
112 112 m.connect("/repos/{repo_name:.*?}",
113 113 action="delete", conditions=dict(method=["DELETE"],
114 114 function=check_repo))
115 115 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
116 116 action="edit", conditions=dict(method=["GET"],
117 117 function=check_repo))
118 118 m.connect("repo", "/repos/{repo_name:.*?}",
119 119 action="show", conditions=dict(method=["GET"],
120 120 function=check_repo))
121 121 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
122 122 action="show", conditions=dict(method=["GET"],
123 123 function=check_repo))
124 124 #add repo perm member
125 m.connect('set_repo_perm_member', "/set_repo_perm_member/{repo_name:.*?}",
125 m.connect('set_repo_perm_member',
126 "/repos/{repo_name:.*?}/grant_perm",
126 127 action="set_repo_perm_member",
127 128 conditions=dict(method=["POST"], function=check_repo))
128 129
129 130 #ajax delete repo perm user
130 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
131 action="delete_perm_user",
132 conditions=dict(method=["DELETE"], function=check_repo))
133
134 #ajax delete repo perm users_group
135 m.connect('delete_repo_users_group',
136 "/repos_delete_users_group/{repo_name:.*?}",
137 action="delete_perm_users_group",
131 m.connect('delete_repo_perm_member',
132 "/repos/{repo_name:.*?}/revoke_perm",
133 action="delete_repo_perm_member",
138 134 conditions=dict(method=["DELETE"], function=check_repo))
139 135
140 136 #settings actions
141 137 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
142 138 action="repo_stats", conditions=dict(method=["DELETE"],
143 139 function=check_repo))
144 140 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
145 141 action="repo_cache", conditions=dict(method=["DELETE"],
146 142 function=check_repo))
147 143 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
148 144 action="repo_public_journal", conditions=dict(method=["PUT"],
149 145 function=check_repo))
150 146 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
151 147 action="repo_pull", conditions=dict(method=["PUT"],
152 148 function=check_repo))
153 149 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
154 150 action="repo_as_fork", conditions=dict(method=["PUT"],
155 151 function=check_repo))
156 152 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
157 153 action="repo_locking", conditions=dict(method=["PUT"],
158 154 function=check_repo))
159 155 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
160 156 action="toggle_locking", conditions=dict(method=["GET"],
161 157 function=check_repo))
162 158
163 159 #repo fields
164 160 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
165 161 action="create_repo_field", conditions=dict(method=["PUT"],
166 162 function=check_repo))
167 163
168 164 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
169 165 action="delete_repo_field", conditions=dict(method=["DELETE"],
170 166 function=check_repo))
171 167
172 168 with rmap.submapper(path_prefix=ADMIN_PREFIX,
173 169 controller='admin/repos_groups') as m:
174 170 m.connect("repos_groups", "/repos_groups",
175 171 action="create", conditions=dict(method=["POST"]))
176 172 m.connect("repos_groups", "/repos_groups",
177 173 action="index", conditions=dict(method=["GET"]))
178 174 m.connect("formatted_repos_groups", "/repos_groups.{format}",
179 175 action="index", conditions=dict(method=["GET"]))
180 176 m.connect("new_repos_group", "/repos_groups/new",
181 177 action="new", conditions=dict(method=["GET"]))
182 178 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
183 179 action="new", conditions=dict(method=["GET"]))
184 180 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
185 181 action="update", conditions=dict(method=["PUT"],
186 182 function=check_group))
183 #add repo group perm member
184 m.connect('set_repo_group_perm_member',
185 "/repos_groups/{group_name:.*?}/grant_perm",
186 action="set_repo_group_perm_member",
187 conditions=dict(method=["POST"], function=check_group))
188
189 #ajax delete repo group perm
190 m.connect('delete_repo_group_perm_member',
191 "/repos_groups/{group_name:.*?}/revoke_perm",
192 action="delete_repo_group_perm_member",
193 conditions=dict(method=["DELETE"], function=check_group))
194
187 195 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
188 196 action="delete", conditions=dict(method=["DELETE"],
189 197 function=check_group_skip_path))
190 198 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
191 199 action="edit", conditions=dict(method=["GET"],
192 200 function=check_group))
193 201 m.connect("formatted_edit_repos_group",
194 202 "/repos_groups/{group_name:.*?}.{format}/edit",
195 203 action="edit", conditions=dict(method=["GET"],
196 204 function=check_group))
197 205 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
198 206 action="show", conditions=dict(method=["GET"],
199 207 function=check_group))
200 208 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
201 209 action="show", conditions=dict(method=["GET"],
202 210 function=check_group))
203 211
204 #add repo perm member
205 m.connect('set_repo_group_perm_member',
206 "/set_repo_group_perm_member/{group_name:.*?}",
207 action="set_repo_group_perm_member",
208 conditions=dict(method=["POST"], function=check_group))
209
210 # ajax delete repository group perm user
211 m.connect('delete_repos_group_user_perm',
212 "/delete_repos_group_user_perm/{group_name:.*?}",
213 action="delete_repos_group_user_perm",
214 conditions=dict(method=["DELETE"], function=check_group))
215
216 # ajax delete repository group perm users_group
217 m.connect('delete_repos_group_users_group_perm',
218 "/delete_repos_group_users_group_perm/{group_name:.*?}",
219 action="delete_repos_group_users_group_perm",
220 conditions=dict(method=["DELETE"], function=check_group))
221
222 212 #ADMIN USER REST ROUTES
223 213 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 214 controller='admin/users') as m:
225 215 m.connect("users", "/users",
226 216 action="create", conditions=dict(method=["POST"]))
227 217 m.connect("users", "/users",
228 218 action="index", conditions=dict(method=["GET"]))
229 219 m.connect("formatted_users", "/users.{format}",
230 220 action="index", conditions=dict(method=["GET"]))
231 221 m.connect("new_user", "/users/new",
232 222 action="new", conditions=dict(method=["GET"]))
233 223 m.connect("formatted_new_user", "/users/new.{format}",
234 224 action="new", conditions=dict(method=["GET"]))
235 225 m.connect("update_user", "/users/{id}",
236 226 action="update", conditions=dict(method=["PUT"]))
237 227 m.connect("delete_user", "/users/{id}",
238 228 action="delete", conditions=dict(method=["DELETE"]))
239 229 m.connect("edit_user", "/users/{id}/edit",
240 230 action="edit", conditions=dict(method=["GET"]))
241 231 m.connect("formatted_edit_user",
242 232 "/users/{id}.{format}/edit",
243 233 action="edit", conditions=dict(method=["GET"]))
244 234 m.connect("user", "/users/{id}",
245 235 action="show", conditions=dict(method=["GET"]))
246 236 m.connect("formatted_user", "/users/{id}.{format}",
247 237 action="show", conditions=dict(method=["GET"]))
248 238
249 239 #EXTRAS USER ROUTES
250 240 m.connect("user_perm", "/users_perm/{id}",
251 241 action="update_perm", conditions=dict(method=["PUT"]))
252 242 m.connect("user_emails", "/users_emails/{id}",
253 243 action="add_email", conditions=dict(method=["PUT"]))
254 244 m.connect("user_emails_delete", "/users_emails/{id}",
255 245 action="delete_email", conditions=dict(method=["DELETE"]))
256 246 m.connect("user_ips", "/users_ips/{id}",
257 247 action="add_ip", conditions=dict(method=["PUT"]))
258 248 m.connect("user_ips_delete", "/users_ips/{id}",
259 249 action="delete_ip", conditions=dict(method=["DELETE"]))
260 250
261 251 #ADMIN USER GROUPS REST ROUTES
262 252 with rmap.submapper(path_prefix=ADMIN_PREFIX,
263 253 controller='admin/users_groups') as m:
264 254 m.connect("users_groups", "/users_groups",
265 255 action="create", conditions=dict(method=["POST"]))
266 256 m.connect("users_groups", "/users_groups",
267 257 action="index", conditions=dict(method=["GET"]))
268 258 m.connect("formatted_users_groups", "/users_groups.{format}",
269 259 action="index", conditions=dict(method=["GET"]))
270 260 m.connect("new_users_group", "/users_groups/new",
271 261 action="new", conditions=dict(method=["GET"]))
272 262 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
273 263 action="new", conditions=dict(method=["GET"]))
274 264 m.connect("update_users_group", "/users_groups/{id}",
275 265 action="update", conditions=dict(method=["PUT"]))
276 266 m.connect("delete_users_group", "/users_groups/{id}",
277 267 action="delete", conditions=dict(method=["DELETE"]))
278 268 m.connect("edit_users_group", "/users_groups/{id}/edit",
279 269 action="edit", conditions=dict(method=["GET"]))
280 270 m.connect("formatted_edit_users_group",
281 271 "/users_groups/{id}.{format}/edit",
282 272 action="edit", conditions=dict(method=["GET"]))
283 273 m.connect("users_group", "/users_groups/{id}",
284 274 action="show", conditions=dict(method=["GET"]))
285 275 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
286 276 action="show", conditions=dict(method=["GET"]))
287 277
288 278 #EXTRAS USER ROUTES
289 279 # update
290 280 m.connect("users_group_perm", "/users_groups/{id}/update_global_perm",
291 281 action="update_perm", conditions=dict(method=["PUT"]))
292 282
293 283 #add user group perm member
294 284 m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm",
295 285 action="set_user_group_perm_member",
296 286 conditions=dict(method=["POST"]))
297 287
298 288 #ajax delete user group perm
299 289 m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm",
300 290 action="delete_user_group_perm_member",
301 291 conditions=dict(method=["DELETE"]))
302 292
303 293 #ADMIN GROUP REST ROUTES
304 294 rmap.resource('group', 'groups',
305 295 controller='admin/groups', path_prefix=ADMIN_PREFIX)
306 296
307 297 #ADMIN PERMISSIONS REST ROUTES
308 298 rmap.resource('permission', 'permissions',
309 299 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
310 300
311 301 #ADMIN DEFAULTS REST ROUTES
312 302 rmap.resource('default', 'defaults',
313 303 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
314 304
315 305 ##ADMIN LDAP SETTINGS
316 306 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
317 307 controller='admin/ldap_settings', action='ldap_settings',
318 308 conditions=dict(method=["POST"]))
319 309
320 310 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
321 311 controller='admin/ldap_settings')
322 312
323 313 #ADMIN SETTINGS REST ROUTES
324 314 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 315 controller='admin/settings') as m:
326 316 m.connect("admin_settings", "/settings",
327 317 action="create", conditions=dict(method=["POST"]))
328 318 m.connect("admin_settings", "/settings",
329 319 action="index", conditions=dict(method=["GET"]))
330 320 m.connect("formatted_admin_settings", "/settings.{format}",
331 321 action="index", conditions=dict(method=["GET"]))
332 322 m.connect("admin_new_setting", "/settings/new",
333 323 action="new", conditions=dict(method=["GET"]))
334 324 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
335 325 action="new", conditions=dict(method=["GET"]))
336 326 m.connect("/settings/{setting_id}",
337 327 action="update", conditions=dict(method=["PUT"]))
338 328 m.connect("/settings/{setting_id}",
339 329 action="delete", conditions=dict(method=["DELETE"]))
340 330 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
341 331 action="edit", conditions=dict(method=["GET"]))
342 332 m.connect("formatted_admin_edit_setting",
343 333 "/settings/{setting_id}.{format}/edit",
344 334 action="edit", conditions=dict(method=["GET"]))
345 335 m.connect("admin_setting", "/settings/{setting_id}",
346 336 action="show", conditions=dict(method=["GET"]))
347 337 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
348 338 action="show", conditions=dict(method=["GET"]))
349 339 m.connect("admin_settings_my_account", "/my_account",
350 340 action="my_account", conditions=dict(method=["GET"]))
351 341 m.connect("admin_settings_my_account_update", "/my_account_update",
352 342 action="my_account_update", conditions=dict(method=["PUT"]))
353 343 m.connect("admin_settings_my_repos", "/my_account/repos",
354 344 action="my_account_my_repos", conditions=dict(method=["GET"]))
355 345 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
356 346 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
357 347
358 348 #NOTIFICATION REST ROUTES
359 349 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 350 controller='admin/notifications') as m:
361 351 m.connect("notifications", "/notifications",
362 352 action="create", conditions=dict(method=["POST"]))
363 353 m.connect("notifications", "/notifications",
364 354 action="index", conditions=dict(method=["GET"]))
365 355 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
366 356 action="mark_all_read", conditions=dict(method=["GET"]))
367 357 m.connect("formatted_notifications", "/notifications.{format}",
368 358 action="index", conditions=dict(method=["GET"]))
369 359 m.connect("new_notification", "/notifications/new",
370 360 action="new", conditions=dict(method=["GET"]))
371 361 m.connect("formatted_new_notification", "/notifications/new.{format}",
372 362 action="new", conditions=dict(method=["GET"]))
373 363 m.connect("/notification/{notification_id}",
374 364 action="update", conditions=dict(method=["PUT"]))
375 365 m.connect("/notification/{notification_id}",
376 366 action="delete", conditions=dict(method=["DELETE"]))
377 367 m.connect("edit_notification", "/notification/{notification_id}/edit",
378 368 action="edit", conditions=dict(method=["GET"]))
379 369 m.connect("formatted_edit_notification",
380 370 "/notification/{notification_id}.{format}/edit",
381 371 action="edit", conditions=dict(method=["GET"]))
382 372 m.connect("notification", "/notification/{notification_id}",
383 373 action="show", conditions=dict(method=["GET"]))
384 374 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
385 375 action="show", conditions=dict(method=["GET"]))
386 376
387 377 #ADMIN MAIN PAGES
388 378 with rmap.submapper(path_prefix=ADMIN_PREFIX,
389 379 controller='admin/admin') as m:
390 380 m.connect('admin_home', '', action='index')
391 381 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
392 382 action='add_repo')
393 383
394 384 #==========================================================================
395 385 # API V2
396 386 #==========================================================================
397 387 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 388 controller='api/api') as m:
399 389 m.connect('api', '/api')
400 390
401 391 #USER JOURNAL
402 392 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
403 393 controller='journal', action='index')
404 394 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
405 395 controller='journal', action='journal_rss')
406 396 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
407 397 controller='journal', action='journal_atom')
408 398
409 399 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
410 400 controller='journal', action="public_journal")
411 401
412 402 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
413 403 controller='journal', action="public_journal_rss")
414 404
415 405 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
416 406 controller='journal', action="public_journal_rss")
417 407
418 408 rmap.connect('public_journal_atom',
419 409 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
420 410 action="public_journal_atom")
421 411
422 412 rmap.connect('public_journal_atom_old',
423 413 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
424 414 action="public_journal_atom")
425 415
426 416 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
427 417 controller='journal', action='toggle_following',
428 418 conditions=dict(method=["POST"]))
429 419
430 420 #SEARCH
431 421 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
432 422 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
433 423 controller='search',
434 424 conditions=dict(function=check_repo))
435 425 rmap.connect('search_repo', '/{repo_name:.*?}/search',
436 426 controller='search',
437 427 conditions=dict(function=check_repo),
438 428 )
439 429
440 430 #LOGIN/LOGOUT/REGISTER/SIGN IN
441 431 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
442 432 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
443 433 action='logout')
444 434
445 435 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
446 436 action='register')
447 437
448 438 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
449 439 controller='login', action='password_reset')
450 440
451 441 rmap.connect('reset_password_confirmation',
452 442 '%s/password_reset_confirmation' % ADMIN_PREFIX,
453 443 controller='login', action='password_reset_confirmation')
454 444
455 445 #FEEDS
456 446 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
457 447 controller='feed', action='rss',
458 448 conditions=dict(function=check_repo))
459 449
460 450 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
461 451 controller='feed', action='atom',
462 452 conditions=dict(function=check_repo))
463 453
464 454 #==========================================================================
465 455 # REPOSITORY ROUTES
466 456 #==========================================================================
467 457 rmap.connect('summary_home', '/{repo_name:.*?}',
468 458 controller='summary',
469 459 conditions=dict(function=check_repo))
470 460
471 461 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
472 462 controller='summary', action='repo_size',
473 463 conditions=dict(function=check_repo))
474 464
475 465 rmap.connect('repos_group_home', '/{group_name:.*}',
476 466 controller='admin/repos_groups', action="show_by_name",
477 467 conditions=dict(function=check_group))
478 468
479 469 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
480 470 controller='changeset', revision='tip',
481 471 conditions=dict(function=check_repo))
482 472
483 473 # no longer user, but kept for routes to work
484 474 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
485 475 controller='admin/repos', action="edit",
486 476 conditions=dict(method=["GET"], function=check_repo)
487 477 )
488 478
489 479 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
490 480 controller='admin/repos', action="edit",
491 481 conditions=dict(method=["GET"], function=check_repo)
492 482 )
493 483
494 484 #still working url for backward compat.
495 485 rmap.connect('raw_changeset_home_depraced',
496 486 '/{repo_name:.*?}/raw-changeset/{revision}',
497 487 controller='changeset', action='changeset_raw',
498 488 revision='tip', conditions=dict(function=check_repo))
499 489
500 490 ## new URLs
501 491 rmap.connect('changeset_raw_home',
502 492 '/{repo_name:.*?}/changeset-diff/{revision}',
503 493 controller='changeset', action='changeset_raw',
504 494 revision='tip', conditions=dict(function=check_repo))
505 495
506 496 rmap.connect('changeset_patch_home',
507 497 '/{repo_name:.*?}/changeset-patch/{revision}',
508 498 controller='changeset', action='changeset_patch',
509 499 revision='tip', conditions=dict(function=check_repo))
510 500
511 501 rmap.connect('changeset_download_home',
512 502 '/{repo_name:.*?}/changeset-download/{revision}',
513 503 controller='changeset', action='changeset_download',
514 504 revision='tip', conditions=dict(function=check_repo))
515 505
516 506 rmap.connect('changeset_comment',
517 507 '/{repo_name:.*?}/changeset/{revision}/comment',
518 508 controller='changeset', revision='tip', action='comment',
519 509 conditions=dict(function=check_repo))
520 510
521 511 rmap.connect('changeset_comment_preview',
522 512 '/{repo_name:.*?}/changeset/comment/preview',
523 513 controller='changeset', action='preview_comment',
524 514 conditions=dict(function=check_repo, method=["POST"]))
525 515
526 516 rmap.connect('changeset_comment_delete',
527 517 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
528 518 controller='changeset', action='delete_comment',
529 519 conditions=dict(function=check_repo, method=["DELETE"]))
530 520
531 521 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
532 522 controller='changeset', action='changeset_info')
533 523
534 524 rmap.connect('compare_url',
535 525 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
536 526 controller='compare', action='index',
537 527 conditions=dict(function=check_repo),
538 528 requirements=dict(
539 529 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
540 530 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
541 531 )
542 532
543 533 rmap.connect('pullrequest_home',
544 534 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
545 535 action='index', conditions=dict(function=check_repo,
546 536 method=["GET"]))
547 537
548 538 rmap.connect('pullrequest',
549 539 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
550 540 action='create', conditions=dict(function=check_repo,
551 541 method=["POST"]))
552 542
553 543 rmap.connect('pullrequest_show',
554 544 '/{repo_name:.*?}/pull-request/{pull_request_id}',
555 545 controller='pullrequests',
556 546 action='show', conditions=dict(function=check_repo,
557 547 method=["GET"]))
558 548 rmap.connect('pullrequest_update',
559 549 '/{repo_name:.*?}/pull-request/{pull_request_id}',
560 550 controller='pullrequests',
561 551 action='update', conditions=dict(function=check_repo,
562 552 method=["PUT"]))
563 553 rmap.connect('pullrequest_delete',
564 554 '/{repo_name:.*?}/pull-request/{pull_request_id}',
565 555 controller='pullrequests',
566 556 action='delete', conditions=dict(function=check_repo,
567 557 method=["DELETE"]))
568 558
569 559 rmap.connect('pullrequest_show_all',
570 560 '/{repo_name:.*?}/pull-request',
571 561 controller='pullrequests',
572 562 action='show_all', conditions=dict(function=check_repo,
573 563 method=["GET"]))
574 564
575 565 rmap.connect('pullrequest_comment',
576 566 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
577 567 controller='pullrequests',
578 568 action='comment', conditions=dict(function=check_repo,
579 569 method=["POST"]))
580 570
581 571 rmap.connect('pullrequest_comment_delete',
582 572 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
583 573 controller='pullrequests', action='delete_comment',
584 574 conditions=dict(function=check_repo, method=["DELETE"]))
585 575
586 576 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
587 577 controller='summary', conditions=dict(function=check_repo))
588 578
589 579 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
590 580 controller='shortlog', conditions=dict(function=check_repo))
591 581
592 582 rmap.connect('shortlog_file_home', '/{repo_name:.*?}/shortlog/{revision}/{f_path:.*}',
593 583 controller='shortlog', f_path=None,
594 584 conditions=dict(function=check_repo))
595 585
596 586 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
597 587 controller='branches', conditions=dict(function=check_repo))
598 588
599 589 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
600 590 controller='tags', conditions=dict(function=check_repo))
601 591
602 592 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
603 593 controller='bookmarks', conditions=dict(function=check_repo))
604 594
605 595 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
606 596 controller='changelog', conditions=dict(function=check_repo))
607 597
608 598 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
609 599 controller='changelog', action='changelog_details',
610 600 conditions=dict(function=check_repo))
611 601
612 602 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
613 603 controller='files', revision='tip', f_path='',
614 604 conditions=dict(function=check_repo))
615 605
616 606 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
617 607 controller='files', revision='tip', f_path='',
618 608 conditions=dict(function=check_repo))
619 609
620 610 rmap.connect('files_history_home',
621 611 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
622 612 controller='files', action='history', revision='tip', f_path='',
623 613 conditions=dict(function=check_repo))
624 614
625 615 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
626 616 controller='files', action='diff', revision='tip', f_path='',
627 617 conditions=dict(function=check_repo))
628 618
629 619 rmap.connect('files_rawfile_home',
630 620 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
631 621 controller='files', action='rawfile', revision='tip',
632 622 f_path='', conditions=dict(function=check_repo))
633 623
634 624 rmap.connect('files_raw_home',
635 625 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
636 626 controller='files', action='raw', revision='tip', f_path='',
637 627 conditions=dict(function=check_repo))
638 628
639 629 rmap.connect('files_annotate_home',
640 630 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
641 631 controller='files', action='index', revision='tip',
642 632 f_path='', annotate=True, conditions=dict(function=check_repo))
643 633
644 634 rmap.connect('files_edit_home',
645 635 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
646 636 controller='files', action='edit', revision='tip',
647 637 f_path='', conditions=dict(function=check_repo))
648 638
649 639 rmap.connect('files_add_home',
650 640 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
651 641 controller='files', action='add', revision='tip',
652 642 f_path='', conditions=dict(function=check_repo))
653 643
654 644 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
655 645 controller='files', action='archivefile',
656 646 conditions=dict(function=check_repo))
657 647
658 648 rmap.connect('files_nodelist_home',
659 649 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
660 650 controller='files', action='nodelist',
661 651 conditions=dict(function=check_repo))
662 652
663 653 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
664 654 controller='forks', action='fork_create',
665 655 conditions=dict(function=check_repo, method=["POST"]))
666 656
667 657 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
668 658 controller='forks', action='fork',
669 659 conditions=dict(function=check_repo))
670 660
671 661 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
672 662 controller='forks', action='forks',
673 663 conditions=dict(function=check_repo))
674 664
675 665 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
676 666 controller='followers', action='followers',
677 667 conditions=dict(function=check_repo))
678 668
679 669 return rmap
@@ -1,580 +1,572 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError, HTTPForbidden
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 import rhodecode
38 38 from rhodecode.lib import helpers as h
39 39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator, NotAnonymous,\
41 41 HasPermissionAny, HasReposGroupPermissionAny, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import action_logger, repo_name_slug
44 44 from rhodecode.lib.helpers import get_token
45 45 from rhodecode.model.meta import Session
46 46 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup,\
47 47 RhodeCodeSetting, RepositoryField
48 48 from rhodecode.model.forms import RepoForm, RepoFieldForm, RepoPermsForm
49 49 from rhodecode.model.scm import ScmModel, RepoGroupList
50 50 from rhodecode.model.repo import RepoModel
51 51 from rhodecode.lib.compat import json
52 52 from sqlalchemy.sql.expression import func
53 53 from rhodecode.lib.exceptions import AttachedForksError
54 from rhodecode.lib.utils2 import safe_int
54 55
55 56 log = logging.getLogger(__name__)
56 57
57 58
58 59 class ReposController(BaseRepoController):
59 60 """
60 61 REST Controller styled on the Atom Publishing Protocol"""
61 62 # To properly map this controller, ensure your config/routing.py
62 63 # file has a resource setup:
63 64 # map.resource('repo', 'repos')
64 65
65 66 @LoginRequired()
66 67 def __before__(self):
67 68 super(ReposController, self).__before__()
68 69
69 70 def __load_defaults(self):
70 71 acl_groups = RepoGroupList(RepoGroup.query().all(),
71 72 perm_set=['group.write', 'group.admin'])
72 73 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
73 74 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
74 75
75 76 repo_model = RepoModel()
76 77 c.users_array = repo_model.get_users_js()
77 78 c.users_groups_array = repo_model.get_users_groups_js()
78 79 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
79 80 c.landing_revs_choices = choices
80 81
81 82 def __load_data(self, repo_name=None):
82 83 """
83 84 Load defaults settings for edit, and update
84 85
85 86 :param repo_name:
86 87 """
87 88 self.__load_defaults()
88 89
89 90 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
90 91 repo = db_repo.scm_instance
91 92
92 93 if c.repo_info is None:
93 94 h.not_mapped_error(repo_name)
94 95 return redirect(url('repos'))
95 96
96 97 ##override defaults for exact repo info here git/hg etc
97 98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
98 99 c.landing_revs_choices = choices
99 100
100 101 c.default_user_id = User.get_by_username('default').user_id
101 102 c.in_public_journal = UserFollowing.query()\
102 103 .filter(UserFollowing.user_id == c.default_user_id)\
103 104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
104 105
105 106 if c.repo_info.stats:
106 107 # this is on what revision we ended up so we add +1 for count
107 108 last_rev = c.repo_info.stats.stat_on_revision + 1
108 109 else:
109 110 last_rev = 0
110 111 c.stats_revision = last_rev
111 112
112 113 c.repo_last_rev = repo.count() if repo.revisions else 0
113 114
114 115 if last_rev == 0 or c.repo_last_rev == 0:
115 116 c.stats_percentage = 0
116 117 else:
117 118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
118 119 c.repo_last_rev) * 100)
119 120
120 121 c.repo_fields = RepositoryField.query()\
121 122 .filter(RepositoryField.repository == db_repo).all()
122 123
123 124 defaults = RepoModel()._get_defaults(repo_name)
124 125
125 126 c.repos_list = [('', _('--REMOVE FORK--'))]
126 127 c.repos_list += [(x.repo_id, x.repo_name) for x in
127 128 Repository.query().order_by(Repository.repo_name).all()
128 129 if x.repo_id != c.repo_info.repo_id]
129 130
130 131 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
131 132 return defaults
132 133
133 134 @HasPermissionAllDecorator('hg.admin')
134 135 def index(self, format='html'):
135 136 """GET /repos: All items in the collection"""
136 137 # url('repos')
137 138
138 139 c.repos_list = Repository.query()\
139 140 .order_by(func.lower(Repository.repo_name))\
140 141 .all()
141 142
142 143 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
143 144 admin=True,
144 145 super_user_actions=True)
145 146 #json used to render the grid
146 147 c.data = json.dumps(repos_data)
147 148
148 149 return render('admin/repos/repos.html')
149 150
150 151 @NotAnonymous()
151 152 def create(self):
152 153 """
153 154 POST /repos: Create a new item"""
154 155 # url('repos')
155 156
156 157 self.__load_defaults()
157 158 form_result = {}
158 159 try:
159 160 form_result = RepoForm(repo_groups=c.repo_groups_choices,
160 161 landing_revs=c.landing_revs_choices)()\
161 162 .to_python(dict(request.POST))
162 163
163 164 new_repo = RepoModel().create(form_result,
164 165 self.rhodecode_user.user_id)
165 166 if form_result['clone_uri']:
166 167 h.flash(_('Created repository %s from %s') \
167 168 % (form_result['repo_name'], form_result['clone_uri']),
168 169 category='success')
169 170 else:
170 171 repo_url = h.link_to(form_result['repo_name'],
171 172 h.url('summary_home', repo_name=form_result['repo_name_full']))
172 173 h.flash(h.literal(_('Created repository %s') % repo_url),
173 174 category='success')
174 175
175 176 if request.POST.get('user_created'):
176 177 # created by regular non admin user
177 178 action_logger(self.rhodecode_user, 'user_created_repo',
178 179 form_result['repo_name_full'], self.ip_addr,
179 180 self.sa)
180 181 else:
181 182 action_logger(self.rhodecode_user, 'admin_created_repo',
182 183 form_result['repo_name_full'], self.ip_addr,
183 184 self.sa)
184 185 Session().commit()
185 186 except formencode.Invalid, errors:
186 187 return htmlfill.render(
187 188 render('admin/repos/repo_add.html'),
188 189 defaults=errors.value,
189 190 errors=errors.error_dict or {},
190 191 prefix_error=False,
191 192 encoding="UTF-8")
192 193
193 194 except Exception:
194 195 log.error(traceback.format_exc())
195 196 msg = _('Error creating repository %s') \
196 197 % form_result.get('repo_name')
197 198 h.flash(msg, category='error')
198 199 if c.rhodecode_user.is_admin:
199 200 return redirect(url('repos'))
200 201 return redirect(url('home'))
201 202 #redirect to our new repo !
202 203 return redirect(url('summary_home', repo_name=new_repo.repo_name))
203 204
204 205 @NotAnonymous()
205 206 def create_repository(self):
206 207 """GET /_admin/create_repository: Form to create a new item"""
207 208 new_repo = request.GET.get('repo', '')
208 209 parent_group = request.GET.get('parent_group')
209 210 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
210 211 #you're not super admin nor have global create permissions,
211 212 #but maybe you have at least write permission to a parent group ?
212 213 _gr = RepoGroup.get(parent_group)
213 214 gr_name = _gr.group_name if _gr else None
214 215 if not HasReposGroupPermissionAny('group.admin', 'group.write')(group_name=gr_name):
215 216 raise HTTPForbidden
216 217
217 218 acl_groups = RepoGroupList(RepoGroup.query().all(),
218 219 perm_set=['group.write', 'group.admin'])
219 220 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
220 221 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
221 222 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
222 223
223 224 c.new_repo = repo_name_slug(new_repo)
224 225
225 226 ## apply the defaults from defaults page
226 227 defaults = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
227 228 if parent_group:
228 229 defaults.update({'repo_group': parent_group})
229 230
230 231 return htmlfill.render(
231 232 render('admin/repos/repo_add.html'),
232 233 defaults=defaults,
233 234 errors={},
234 235 prefix_error=False,
235 236 encoding="UTF-8"
236 237 )
237 238
238 239 @HasRepoPermissionAllDecorator('repository.admin')
239 240 def update(self, repo_name):
240 241 """
241 242 PUT /repos/repo_name: Update an existing item"""
242 243 # Forms posted to this method should contain a hidden field:
243 244 # <input type="hidden" name="_method" value="PUT" />
244 245 # Or using helpers:
245 246 # h.form(url('repo', repo_name=ID),
246 247 # method='put')
247 248 # url('repo', repo_name=ID)
248 249 self.__load_defaults()
249 250 repo_model = RepoModel()
250 251 changed_name = repo_name
251 252 #override the choices with extracted revisions !
252 253 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
253 254 c.landing_revs_choices = choices
254 255 repo = Repository.get_by_repo_name(repo_name)
255 256 _form = RepoForm(edit=True, old_data={'repo_name': repo_name,
256 257 'repo_group': repo.group.get_dict() \
257 258 if repo.group else {}},
258 259 repo_groups=c.repo_groups_choices,
259 260 landing_revs=c.landing_revs_choices)()
260 261 try:
261 262 form_result = _form.to_python(dict(request.POST))
262 263 repo = repo_model.update(repo_name, **form_result)
263 264 ScmModel().mark_for_invalidation(repo_name)
264 265 h.flash(_('Repository %s updated successfully') % repo_name,
265 266 category='success')
266 267 changed_name = repo.repo_name
267 268 action_logger(self.rhodecode_user, 'admin_updated_repo',
268 269 changed_name, self.ip_addr, self.sa)
269 270 Session().commit()
270 271 except formencode.Invalid, errors:
271 272 defaults = self.__load_data(repo_name)
272 273 defaults.update(errors.value)
273 274 return htmlfill.render(
274 275 render('admin/repos/repo_edit.html'),
275 276 defaults=defaults,
276 277 errors=errors.error_dict or {},
277 278 prefix_error=False,
278 279 encoding="UTF-8")
279 280
280 281 except Exception:
281 282 log.error(traceback.format_exc())
282 283 h.flash(_('Error occurred during update of repository %s') \
283 284 % repo_name, category='error')
284 285 return redirect(url('edit_repo', repo_name=changed_name))
285 286
286 287 @HasRepoPermissionAllDecorator('repository.admin')
287 288 def delete(self, repo_name):
288 289 """
289 290 DELETE /repos/repo_name: Delete an existing item"""
290 291 # Forms posted to this method should contain a hidden field:
291 292 # <input type="hidden" name="_method" value="DELETE" />
292 293 # Or using helpers:
293 294 # h.form(url('repo', repo_name=ID),
294 295 # method='delete')
295 296 # url('repo', repo_name=ID)
296 297
297 298 repo_model = RepoModel()
298 299 repo = repo_model.get_by_repo_name(repo_name)
299 300 if not repo:
300 301 h.not_mapped_error(repo_name)
301 302 return redirect(url('repos'))
302 303 try:
303 304 _forks = repo.forks.count()
304 305 handle_forks = None
305 306 if _forks and request.POST.get('forks'):
306 307 do = request.POST['forks']
307 308 if do == 'detach_forks':
308 309 handle_forks = 'detach'
309 310 h.flash(_('Detached %s forks') % _forks, category='success')
310 311 elif do == 'delete_forks':
311 312 handle_forks = 'delete'
312 313 h.flash(_('Deleted %s forks') % _forks, category='success')
313 314 repo_model.delete(repo, forks=handle_forks)
314 315 action_logger(self.rhodecode_user, 'admin_deleted_repo',
315 316 repo_name, self.ip_addr, self.sa)
316 317 ScmModel().mark_for_invalidation(repo_name)
317 318 h.flash(_('Deleted repository %s') % repo_name, category='success')
318 319 Session().commit()
319 320 except AttachedForksError:
320 321 h.flash(_('Cannot delete %s it still contains attached forks')
321 322 % repo_name, category='warning')
322 323
323 324 except Exception:
324 325 log.error(traceback.format_exc())
325 326 h.flash(_('An error occurred during deletion of %s') % repo_name,
326 327 category='error')
327 328
328 329 return redirect(url('repos'))
329 330
330 331 @HasRepoPermissionAllDecorator('repository.admin')
331 332 def set_repo_perm_member(self, repo_name):
332 333 form = RepoPermsForm()().to_python(request.POST)
333 334 RepoModel()._update_permissions(repo_name, form['perms_new'],
334 335 form['perms_updates'])
335 336 #TODO: implement this
336 337 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
337 338 # repo_name, self.ip_addr, self.sa)
338 339 Session().commit()
339 340 h.flash(_('Repository permissions updated'), category='success')
340 341 return redirect(url('edit_repo', repo_name=repo_name))
341 342
342 343 @HasRepoPermissionAllDecorator('repository.admin')
343 def delete_perm_user(self, repo_name):
344 def delete_repo_perm_member(self, repo_name):
344 345 """
345 346 DELETE an existing repository permission user
346 347
347 348 :param repo_name:
348 349 """
349 350 try:
350 RepoModel().revoke_user_permission(repo=repo_name,
351 user=request.POST['user_id'])
351 obj_type = request.POST.get('obj_type')
352 obj_id = None
353 if obj_type == 'user':
354 obj_id = safe_int(request.POST.get('user_id'))
355 elif obj_type == 'user_group':
356 obj_id = safe_int(request.POST.get('user_group_id'))
357
358 if obj_type == 'user':
359 RepoModel().revoke_user_permission(repo=repo_name, user=obj_id)
360 elif obj_type == 'user_group':
361 RepoModel().revoke_users_group_permission(
362 repo=repo_name, group_name=obj_id
363 )
352 364 #TODO: implement this
353 365 #action_logger(self.rhodecode_user, 'admin_revoked_repo_permissions',
354 366 # repo_name, self.ip_addr, self.sa)
355 367 Session().commit()
356 368 except Exception:
357 369 log.error(traceback.format_exc())
358 h.flash(_('An error occurred during deletion of repository user'),
359 category='error')
360 raise HTTPInternalServerError()
361
362 @HasRepoPermissionAllDecorator('repository.admin')
363 def delete_perm_users_group(self, repo_name):
364 """
365 DELETE an existing repository permission user group
366
367 :param repo_name:
368 """
369
370 try:
371 RepoModel().revoke_users_group_permission(
372 repo=repo_name, group_name=request.POST['users_group_id']
373 )
374 Session().commit()
375 except Exception:
376 log.error(traceback.format_exc())
377 h.flash(_('An error occurred during deletion of repository'
378 ' user groups'),
370 h.flash(_('An error occurred during revoking of permission'),
379 371 category='error')
380 372 raise HTTPInternalServerError()
381 373
382 374 @HasRepoPermissionAllDecorator('repository.admin')
383 375 def repo_stats(self, repo_name):
384 376 """
385 377 DELETE an existing repository statistics
386 378
387 379 :param repo_name:
388 380 """
389 381
390 382 try:
391 383 RepoModel().delete_stats(repo_name)
392 384 Session().commit()
393 385 except Exception, e:
394 386 log.error(traceback.format_exc())
395 387 h.flash(_('An error occurred during deletion of repository stats'),
396 388 category='error')
397 389 return redirect(url('edit_repo', repo_name=repo_name))
398 390
399 391 @HasRepoPermissionAllDecorator('repository.admin')
400 392 def repo_cache(self, repo_name):
401 393 """
402 394 INVALIDATE existing repository cache
403 395
404 396 :param repo_name:
405 397 """
406 398
407 399 try:
408 400 ScmModel().mark_for_invalidation(repo_name)
409 401 Session().commit()
410 402 except Exception, e:
411 403 log.error(traceback.format_exc())
412 404 h.flash(_('An error occurred during cache invalidation'),
413 405 category='error')
414 406 return redirect(url('edit_repo', repo_name=repo_name))
415 407
416 408 @HasRepoPermissionAllDecorator('repository.admin')
417 409 def repo_locking(self, repo_name):
418 410 """
419 411 Unlock repository when it is locked !
420 412
421 413 :param repo_name:
422 414 """
423 415
424 416 try:
425 417 repo = Repository.get_by_repo_name(repo_name)
426 418 if request.POST.get('set_lock'):
427 419 Repository.lock(repo, c.rhodecode_user.user_id)
428 420 elif request.POST.get('set_unlock'):
429 421 Repository.unlock(repo)
430 422 except Exception, e:
431 423 log.error(traceback.format_exc())
432 424 h.flash(_('An error occurred during unlocking'),
433 425 category='error')
434 426 return redirect(url('edit_repo', repo_name=repo_name))
435 427
436 428 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
437 429 def toggle_locking(self, repo_name):
438 430 """
439 431 Toggle locking of repository by simple GET call to url
440 432
441 433 :param repo_name:
442 434 """
443 435
444 436 try:
445 437 repo = Repository.get_by_repo_name(repo_name)
446 438
447 439 if repo.enable_locking:
448 440 if repo.locked[0]:
449 441 Repository.unlock(repo)
450 442 action = _('Unlocked')
451 443 else:
452 444 Repository.lock(repo, c.rhodecode_user.user_id)
453 445 action = _('Locked')
454 446
455 447 h.flash(_('Repository has been %s') % action,
456 448 category='success')
457 449 except Exception, e:
458 450 log.error(traceback.format_exc())
459 451 h.flash(_('An error occurred during unlocking'),
460 452 category='error')
461 453 return redirect(url('summary_home', repo_name=repo_name))
462 454
463 455 @HasRepoPermissionAllDecorator('repository.admin')
464 456 def repo_public_journal(self, repo_name):
465 457 """
466 458 Set's this repository to be visible in public journal,
467 459 in other words assing default user to follow this repo
468 460
469 461 :param repo_name:
470 462 """
471 463
472 464 cur_token = request.POST.get('auth_token')
473 465 token = get_token()
474 466 if cur_token == token:
475 467 try:
476 468 repo_id = Repository.get_by_repo_name(repo_name).repo_id
477 469 user_id = User.get_by_username('default').user_id
478 470 self.scm_model.toggle_following_repo(repo_id, user_id)
479 471 h.flash(_('Updated repository visibility in public journal'),
480 472 category='success')
481 473 Session().commit()
482 474 except Exception:
483 475 h.flash(_('An error occurred during setting this'
484 476 ' repository in public journal'),
485 477 category='error')
486 478
487 479 else:
488 480 h.flash(_('Token mismatch'), category='error')
489 481 return redirect(url('edit_repo', repo_name=repo_name))
490 482
491 483 @HasRepoPermissionAllDecorator('repository.admin')
492 484 def repo_pull(self, repo_name):
493 485 """
494 486 Runs task to update given repository with remote changes,
495 487 ie. make pull on remote location
496 488
497 489 :param repo_name:
498 490 """
499 491 try:
500 492 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
501 493 h.flash(_('Pulled from remote location'), category='success')
502 494 except Exception, e:
503 495 h.flash(_('An error occurred during pull from remote location'),
504 496 category='error')
505 497
506 498 return redirect(url('edit_repo', repo_name=repo_name))
507 499
508 500 @HasRepoPermissionAllDecorator('repository.admin')
509 501 def repo_as_fork(self, repo_name):
510 502 """
511 503 Mark given repository as a fork of another
512 504
513 505 :param repo_name:
514 506 """
515 507 try:
516 508 fork_id = request.POST.get('id_fork_of')
517 509 repo = ScmModel().mark_as_fork(repo_name, fork_id,
518 510 self.rhodecode_user.username)
519 511 fork = repo.fork.repo_name if repo.fork else _('Nothing')
520 512 Session().commit()
521 513 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
522 514 category='success')
523 515 except Exception, e:
524 516 log.error(traceback.format_exc())
525 517 h.flash(_('An error occurred during this operation'),
526 518 category='error')
527 519
528 520 return redirect(url('edit_repo', repo_name=repo_name))
529 521
530 522 @HasPermissionAllDecorator('hg.admin')
531 523 def show(self, repo_name, format='html'):
532 524 """GET /repos/repo_name: Show a specific item"""
533 525 # url('repo', repo_name=ID)
534 526
535 527 @HasRepoPermissionAllDecorator('repository.admin')
536 528 def edit(self, repo_name, format='html'):
537 529 """GET /repos/repo_name/edit: Form to edit an existing item"""
538 530 # url('edit_repo', repo_name=ID)
539 531 defaults = self.__load_data(repo_name)
540 532
541 533 return htmlfill.render(
542 534 render('admin/repos/repo_edit.html'),
543 535 defaults=defaults,
544 536 encoding="UTF-8",
545 537 force_defaults=False
546 538 )
547 539
548 540 @HasPermissionAllDecorator('hg.admin')
549 541 def create_repo_field(self, repo_name):
550 542 try:
551 543 form_result = RepoFieldForm()().to_python(dict(request.POST))
552 544 new_field = RepositoryField()
553 545 new_field.repository = Repository.get_by_repo_name(repo_name)
554 546 new_field.field_key = form_result['new_field_key']
555 547 new_field.field_type = form_result['new_field_type'] # python type
556 548 new_field.field_value = form_result['new_field_value'] # set initial blank value
557 549 new_field.field_desc = form_result['new_field_desc']
558 550 new_field.field_label = form_result['new_field_label']
559 551 Session().add(new_field)
560 552 Session().commit()
561 553
562 554 except Exception, e:
563 555 log.error(traceback.format_exc())
564 556 msg = _('An error occurred during creation of field')
565 557 if isinstance(e, formencode.Invalid):
566 558 msg += ". " + e.msg
567 559 h.flash(msg, category='error')
568 560 return redirect(url('edit_repo', repo_name=repo_name))
569 561
570 562 @HasPermissionAllDecorator('hg.admin')
571 563 def delete_repo_field(self, repo_name, field_id):
572 564 field = RepositoryField.get_or_404(field_id)
573 565 try:
574 566 Session().delete(field)
575 567 Session().commit()
576 568 except Exception, e:
577 569 log.error(traceback.format_exc())
578 570 msg = _('An error occurred during removal of field')
579 571 h.flash(msg, category='error')
580 572 return redirect(url('edit_repo', repo_name=repo_name))
@@ -1,412 +1,404 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repository groups controller for RhodeCode
7 7
8 8 :created_on: Mar 23, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31
32 32 from pylons import request, tmpl_context as c, url
33 33 from pylons.controllers.util import abort, redirect
34 34 from pylons.i18n.translation import _
35 35
36 36 from sqlalchemy.exc import IntegrityError
37 37
38 38 import rhodecode
39 39 from rhodecode.lib import helpers as h
40 40 from rhodecode.lib.compat import json
41 41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 43 HasPermissionAll
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.model.db import RepoGroup, Repository
46 46 from rhodecode.model.scm import RepoGroupList
47 47 from rhodecode.model.repos_group import ReposGroupModel
48 48 from rhodecode.model.forms import ReposGroupForm, RepoGroupPermsForm
49 49 from rhodecode.model.meta import Session
50 50 from rhodecode.model.repo import RepoModel
51 51 from webob.exc import HTTPInternalServerError, HTTPNotFound
52 52 from rhodecode.lib.utils2 import str2bool, safe_int
53 53 from sqlalchemy.sql.expression import func
54 54
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class ReposGroupsController(BaseController):
60 60 """REST Controller styled on the Atom Publishing Protocol"""
61 61 # To properly map this controller, ensure your config/routing.py
62 62 # file has a resource setup:
63 63 # map.resource('repos_group', 'repos_groups')
64 64
65 65 @LoginRequired()
66 66 def __before__(self):
67 67 super(ReposGroupsController, self).__before__()
68 68
69 69 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
70 70 if HasPermissionAll('hg.admin')('group edit'):
71 71 #we're global admin, we're ok and we can create TOP level groups
72 72 allow_empty_group = True
73 73
74 74 #override the choices for this form, we need to filter choices
75 75 #and display only those we have ADMIN right
76 76 groups_with_admin_rights = RepoGroupList(RepoGroup.query().all(),
77 77 perm_set=['group.admin'])
78 78 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
79 79 show_empty_group=allow_empty_group)
80 80 # exclude filtered ids
81 81 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
82 82 c.repo_groups)
83 83 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
84 84 repo_model = RepoModel()
85 85 c.users_array = repo_model.get_users_js()
86 86 c.users_groups_array = repo_model.get_users_groups_js()
87 87
88 88 def __load_data(self, group_id):
89 89 """
90 90 Load defaults settings for edit, and update
91 91
92 92 :param group_id:
93 93 """
94 94 repo_group = RepoGroup.get_or_404(group_id)
95 95 data = repo_group.get_dict()
96 96 data['group_name'] = repo_group.name
97 97
98 98 # fill repository group users
99 99 for p in repo_group.repo_group_to_perm:
100 100 data.update({'u_perm_%s' % p.user.username:
101 101 p.permission.permission_name})
102 102
103 103 # fill repository group groups
104 104 for p in repo_group.users_group_to_perm:
105 105 data.update({'g_perm_%s' % p.users_group.users_group_name:
106 106 p.permission.permission_name})
107 107
108 108 return data
109 109
110 110 def _revoke_perms_on_yourself(self, form_result):
111 111 _up = filter(lambda u: c.rhodecode_user.username == u[0],
112 112 form_result['perms_updates'])
113 113 _new = filter(lambda u: c.rhodecode_user.username == u[0],
114 114 form_result['perms_new'])
115 115 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
116 116 return True
117 117 return False
118 118
119 119 def index(self, format='html'):
120 120 """GET /repos_groups: All items in the collection"""
121 121 # url('repos_groups')
122 122 group_iter = RepoGroupList(RepoGroup.query().all(),
123 123 perm_set=['group.admin'])
124 124 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
125 125 c.groups = sorted(group_iter, key=sk)
126 126 return render('admin/repos_groups/repos_groups_show.html')
127 127
128 128 def create(self):
129 129 """POST /repos_groups: Create a new item"""
130 130 # url('repos_groups')
131 131
132 132 self.__load_defaults()
133 133
134 134 # permissions for can create group based on parent_id are checked
135 135 # here in the Form
136 136 repos_group_form = ReposGroupForm(available_groups=
137 137 map(lambda k: unicode(k[0]), c.repo_groups))()
138 138 try:
139 139 form_result = repos_group_form.to_python(dict(request.POST))
140 140 ReposGroupModel().create(
141 141 group_name=form_result['group_name'],
142 142 group_description=form_result['group_description'],
143 143 parent=form_result['group_parent_id'],
144 144 owner=self.rhodecode_user.user_id
145 145 )
146 146 Session().commit()
147 147 h.flash(_('Created repository group %s') \
148 148 % form_result['group_name'], category='success')
149 149 #TODO: in futureaction_logger(, '', '', '', self.sa)
150 150 except formencode.Invalid, errors:
151 151 return htmlfill.render(
152 152 render('admin/repos_groups/repos_groups_add.html'),
153 153 defaults=errors.value,
154 154 errors=errors.error_dict or {},
155 155 prefix_error=False,
156 156 encoding="UTF-8")
157 157 except Exception:
158 158 log.error(traceback.format_exc())
159 159 h.flash(_('Error occurred during creation of repository group %s') \
160 160 % request.POST.get('group_name'), category='error')
161 161 parent_group_id = form_result['group_parent_id']
162 162 #TODO: maybe we should get back to the main view, not the admin one
163 163 return redirect(url('repos_groups', parent_group=parent_group_id))
164 164
165 165 def new(self, format='html'):
166 166 """GET /repos_groups/new: Form to create a new item"""
167 167 # url('new_repos_group')
168 168 if HasPermissionAll('hg.admin')('group create'):
169 169 #we're global admin, we're ok and we can create TOP level groups
170 170 pass
171 171 else:
172 172 # we pass in parent group into creation form, thus we know
173 173 # what would be the group, we can check perms here !
174 174 group_id = safe_int(request.GET.get('parent_group'))
175 175 group = RepoGroup.get(group_id) if group_id else None
176 176 group_name = group.group_name if group else None
177 177 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
178 178 pass
179 179 else:
180 180 return abort(403)
181 181
182 182 self.__load_defaults()
183 183 return render('admin/repos_groups/repos_groups_add.html')
184 184
185 185 @HasReposGroupPermissionAnyDecorator('group.admin')
186 186 def update(self, group_name):
187 187 """PUT /repos_groups/group_name: Update an existing item"""
188 188 # Forms posted to this method should contain a hidden field:
189 189 # <input type="hidden" name="_method" value="PUT" />
190 190 # Or using helpers:
191 191 # h.form(url('repos_group', group_name=GROUP_NAME),
192 192 # method='put')
193 193 # url('repos_group', group_name=GROUP_NAME)
194 194
195 195 c.repos_group = ReposGroupModel()._get_repo_group(group_name)
196 196 if HasPermissionAll('hg.admin')('group edit'):
197 197 #we're global admin, we're ok and we can create TOP level groups
198 198 allow_empty_group = True
199 199 elif not c.repos_group.parent_group:
200 200 allow_empty_group = True
201 201 else:
202 202 allow_empty_group = False
203 203 self.__load_defaults(allow_empty_group=allow_empty_group,
204 204 exclude_group_ids=[c.repos_group.group_id])
205 205
206 206 repos_group_form = ReposGroupForm(
207 207 edit=True,
208 208 old_data=c.repos_group.get_dict(),
209 209 available_groups=c.repo_groups_choices,
210 210 can_create_in_root=allow_empty_group,
211 211 )()
212 212 try:
213 213 form_result = repos_group_form.to_python(dict(request.POST))
214 214 if not c.rhodecode_user.is_admin:
215 215 if self._revoke_perms_on_yourself(form_result):
216 216 msg = _('Cannot revoke permission for yourself as admin')
217 217 h.flash(msg, category='warning')
218 218 raise Exception('revoke admin permission on self')
219 219
220 220 new_gr = ReposGroupModel().update(group_name, form_result)
221 221 Session().commit()
222 222 h.flash(_('Updated repository group %s') \
223 223 % form_result['group_name'], category='success')
224 224 # we now have new name !
225 225 group_name = new_gr.group_name
226 226 #TODO: in future action_logger(, '', '', '', self.sa)
227 227 except formencode.Invalid, errors:
228 228
229 229 return htmlfill.render(
230 230 render('admin/repos_groups/repos_groups_edit.html'),
231 231 defaults=errors.value,
232 232 errors=errors.error_dict or {},
233 233 prefix_error=False,
234 234 encoding="UTF-8")
235 235 except Exception:
236 236 log.error(traceback.format_exc())
237 237 h.flash(_('Error occurred during update of repository group %s') \
238 238 % request.POST.get('group_name'), category='error')
239 239
240 240 return redirect(url('edit_repos_group', group_name=group_name))
241 241
242 242 @HasReposGroupPermissionAnyDecorator('group.admin')
243 243 def delete(self, group_name):
244 244 """DELETE /repos_groups/group_name: Delete an existing item"""
245 245 # Forms posted to this method should contain a hidden field:
246 246 # <input type="hidden" name="_method" value="DELETE" />
247 247 # Or using helpers:
248 248 # h.form(url('repos_group', group_name=GROUP_NAME),
249 249 # method='delete')
250 250 # url('repos_group', group_name=GROUP_NAME)
251 251
252 252 gr = c.repos_group = ReposGroupModel()._get_repo_group(group_name)
253 253 repos = gr.repositories.all()
254 254 if repos:
255 255 h.flash(_('This group contains %s repositores and cannot be '
256 256 'deleted') % len(repos), category='warning')
257 257 return redirect(url('repos_groups'))
258 258
259 259 children = gr.children.all()
260 260 if children:
261 261 h.flash(_('This group contains %s subgroups and cannot be deleted'
262 262 % (len(children))), category='warning')
263 263 return redirect(url('repos_groups'))
264 264
265 265 try:
266 266 ReposGroupModel().delete(group_name)
267 267 Session().commit()
268 268 h.flash(_('Removed repository group %s') % group_name,
269 269 category='success')
270 270 #TODO: in future action_logger(, '', '', '', self.sa)
271 271 except Exception:
272 272 log.error(traceback.format_exc())
273 273 h.flash(_('Error occurred during deletion of repos '
274 274 'group %s') % group_name, category='error')
275 275
276 276 return redirect(url('repos_groups'))
277 277
278 278 @HasReposGroupPermissionAnyDecorator('group.admin')
279 279 def set_repo_group_perm_member(self, group_name):
280 280 c.repos_group = ReposGroupModel()._get_repo_group(group_name)
281 281 form = RepoGroupPermsForm()().to_python(request.POST)
282 282
283 283 recursive = form['recursive']
284 284 # iterate over all members(if in recursive mode) of this groups and
285 285 # set the permissions !
286 286 # this can be potentially heavy operation
287 287 ReposGroupModel()._update_permissions(c.repos_group, form['perms_new'],
288 288 form['perms_updates'], recursive)
289 289 #TODO: implement this
290 290 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
291 291 # repo_name, self.ip_addr, self.sa)
292 292 Session().commit()
293 293 h.flash(_('Repository Group permissions updated'), category='success')
294 294 return redirect(url('edit_repos_group', group_name=group_name))
295 295
296 296 @HasReposGroupPermissionAnyDecorator('group.admin')
297 def delete_repos_group_user_perm(self, group_name):
297 def delete_repo_group_perm_member(self, group_name):
298 298 """
299 299 DELETE an existing repository group permission user
300 300
301 301 :param group_name:
302 302 """
303 303 try:
304 obj_type = request.POST.get('obj_type')
305 obj_id = None
306 if obj_type == 'user':
307 obj_id = safe_int(request.POST.get('user_id'))
308 elif obj_type == 'user_group':
309 obj_id = safe_int(request.POST.get('user_group_id'))
310
304 311 if not c.rhodecode_user.is_admin:
305 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
312 if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
306 313 msg = _('Cannot revoke permission for yourself as admin')
307 314 h.flash(msg, category='warning')
308 315 raise Exception('revoke admin permission on self')
309 316 recursive = str2bool(request.POST.get('recursive', False))
317 if obj_type == 'user':
310 318 ReposGroupModel().delete_permission(
311 repos_group=group_name, obj=request.POST['user_id'],
319 repos_group=group_name, obj=obj_id,
312 320 obj_type='user', recursive=recursive
313 321 )
322 elif obj_type == 'user_group':
323 ReposGroupModel().delete_permission(
324 repos_group=group_name, obj=obj_id,
325 obj_type='users_group', recursive=recursive
326 )
327
314 328 Session().commit()
315 329 except Exception:
316 330 log.error(traceback.format_exc())
317 h.flash(_('An error occurred during deletion of group user'),
318 category='error')
319 raise HTTPInternalServerError()
320
321 @HasReposGroupPermissionAnyDecorator('group.admin')
322 def delete_repos_group_users_group_perm(self, group_name):
323 """
324 DELETE an existing repository group permission user group
325
326 :param group_name:
327 """
328
329 try:
330 recursive = str2bool(request.POST.get('recursive', False))
331 ReposGroupModel().delete_permission(
332 repos_group=group_name, obj=request.POST['users_group_id'],
333 obj_type='users_group', recursive=recursive
334 )
335 Session().commit()
336 except Exception:
337 log.error(traceback.format_exc())
338 h.flash(_('An error occurred during deletion of group'
339 ' user groups'),
331 h.flash(_('An error occurred during revoking of permission'),
340 332 category='error')
341 333 raise HTTPInternalServerError()
342 334
343 335 def show_by_name(self, group_name):
344 336 """
345 337 This is a proxy that does a lookup group_name -> id, and shows
346 338 the group by id view instead
347 339 """
348 340 group_name = group_name.rstrip('/')
349 341 id_ = RepoGroup.get_by_group_name(group_name)
350 342 if id_:
351 343 return self.show(id_.group_id)
352 344 raise HTTPNotFound
353 345
354 346 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
355 347 'group.admin')
356 348 def show(self, group_name, format='html'):
357 349 """GET /repos_groups/group_name: Show a specific item"""
358 350 # url('repos_group', group_name=GROUP_NAME)
359 351
360 352 c.group = c.repos_group = ReposGroupModel()._get_repo_group(group_name)
361 353 c.group_repos = c.group.repositories.all()
362 354
363 355 #overwrite our cached list with current filter
364 356 gr_filter = c.group_repos
365 357 c.repo_cnt = 0
366 358
367 359 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
368 360 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
369 361 c.groups = self.scm_model.get_repos_groups(groups)
370 362
371 363 if not c.visual.lightweight_dashboard:
372 364 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
373 365 ## lightweight version of dashboard
374 366 else:
375 367 c.repos_list = Repository.query()\
376 368 .filter(Repository.group_id == c.group.group_id)\
377 369 .order_by(func.lower(Repository.repo_name))\
378 370 .all()
379 371
380 372 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
381 373 admin=False)
382 374 #json used to render the grid
383 375 c.data = json.dumps(repos_data)
384 376
385 377 return render('admin/repos_groups/repos_groups.html')
386 378
387 379 @HasReposGroupPermissionAnyDecorator('group.admin')
388 380 def edit(self, group_name, format='html'):
389 381 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
390 382 # url('edit_repos_group', group_name=GROUP_NAME)
391 383
392 384 c.repos_group = ReposGroupModel()._get_repo_group(group_name)
393 385 #we can only allow moving empty group if it's already a top-level
394 386 #group, ie has no parents, or we're admin
395 387 if HasPermissionAll('hg.admin')('group edit'):
396 388 #we're global admin, we're ok and we can create TOP level groups
397 389 allow_empty_group = True
398 390 elif not c.repos_group.parent_group:
399 391 allow_empty_group = True
400 392 else:
401 393 allow_empty_group = False
402 394
403 395 self.__load_defaults(allow_empty_group=allow_empty_group,
404 396 exclude_group_ids=[c.repos_group.group_id])
405 397 defaults = self.__load_data(c.repos_group.group_id)
406 398
407 399 return htmlfill.render(
408 400 render('admin/repos_groups/repos_groups_edit.html'),
409 401 defaults=defaults,
410 402 encoding="UTF-8",
411 403 force_defaults=False
412 404 )
@@ -1,367 +1,367 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.users_groups
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 User Groups crud controller for pylons
7 7
8 8 :created_on: Jan 25, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29
30 30 from formencode import htmlfill
31 31 from pylons import request, session, tmpl_context as c, url, config
32 32 from pylons.controllers.util import abort, redirect
33 33 from pylons.i18n.translation import _
34 34
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.exceptions import UserGroupsAssignedException
37 37 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator,\
39 39 HasUserGroupPermissionAnyDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.model.scm import UserGroupList
42 42 from rhodecode.model.users_group import UserGroupModel
43 43 from rhodecode.model.repo import RepoModel
44 44 from rhodecode.model.db import User, UserGroup, UserGroupToPerm,\
45 45 UserGroupRepoToPerm, UserGroupRepoGroupToPerm
46 46 from rhodecode.model.forms import UserGroupForm, UserGroupPermsForm
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.lib.utils import action_logger
49 49 from sqlalchemy.orm import joinedload
50 50 from webob.exc import HTTPInternalServerError
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54
55 55 class UsersGroupsController(BaseController):
56 56 """REST Controller styled on the Atom Publishing Protocol"""
57 57 # To properly map this controller, ensure your config/routing.py
58 58 # file has a resource setup:
59 59 # map.resource('users_group', 'users_groups')
60 60
61 61 @LoginRequired()
62 62 def __before__(self):
63 63 super(UsersGroupsController, self).__before__()
64 64 c.available_permissions = config['available_permissions']
65 65
66 66 def __load_data(self, user_group_id):
67 67 ugroup_repo_perms = UserGroupRepoToPerm.query()\
68 68 .options(joinedload(UserGroupRepoToPerm.permission))\
69 69 .options(joinedload(UserGroupRepoToPerm.repository))\
70 70 .filter(UserGroupRepoToPerm.users_group_id == user_group_id)\
71 71 .all()
72 72
73 73 for gr in ugroup_repo_perms:
74 74 c.users_group.permissions['repositories'][gr.repository.repo_name] \
75 75 = gr.permission.permission_name
76 76
77 77 ugroup_group_perms = UserGroupRepoGroupToPerm.query()\
78 78 .options(joinedload(UserGroupRepoGroupToPerm.permission))\
79 79 .options(joinedload(UserGroupRepoGroupToPerm.group))\
80 80 .filter(UserGroupRepoGroupToPerm.users_group_id == user_group_id)\
81 81 .all()
82 82
83 83 for gr in ugroup_group_perms:
84 84 c.users_group.permissions['repositories_groups'][gr.group.group_name] \
85 85 = gr.permission.permission_name
86 86
87 87 c.group_members_obj = sorted((x.user for x in c.users_group.members),
88 88 key=lambda u: u.username.lower())
89 89
90 90 c.group_members = [(x.user_id, x.username) for x in c.group_members_obj]
91 91 c.available_members = sorted(((x.user_id, x.username) for x in
92 92 User.query().all()),
93 93 key=lambda u: u[1].lower())
94 94 repo_model = RepoModel()
95 95 c.users_array = repo_model.get_users_js()
96 96
97 97 # commented out due to not now supporting assignment for user group
98 98 # on user group
99 99 c.users_groups_array = "[]" # repo_model.get_users_groups_js()
100 100 c.available_permissions = config['available_permissions']
101 101
102 102 def __load_defaults(self, user_group_id):
103 103 """
104 104 Load defaults settings for edit, and update
105 105
106 106 :param user_group_id:
107 107 """
108 108 user_group = UserGroup.get_or_404(user_group_id)
109 109 data = user_group.get_dict()
110 110
111 111 ug_model = UserGroupModel()
112 112
113 113 data.update({
114 114 'create_repo_perm': ug_model.has_perm(user_group,
115 115 'hg.create.repository'),
116 116 'fork_repo_perm': ug_model.has_perm(user_group,
117 117 'hg.fork.repository'),
118 118 })
119 119
120 120 # fill user group users
121 121 for p in user_group.user_user_group_to_perm:
122 122 data.update({'u_perm_%s' % p.user.username:
123 123 p.permission.permission_name})
124 124
125 125 return data
126 126
127 127 def index(self, format='html'):
128 128 """GET /users_groups: All items in the collection"""
129 129 # url('users_groups')
130 130
131 131 group_iter = UserGroupList(UserGroup().query().all(),
132 132 perm_set=['usergroup.admin'])
133 133 sk = lambda g: g.users_group_name
134 134 c.users_groups_list = sorted(group_iter, key=sk)
135 135 return render('admin/users_groups/users_groups.html')
136 136
137 137 @HasPermissionAllDecorator('hg.admin')
138 138 def create(self):
139 139 """POST /users_groups: Create a new item"""
140 140 # url('users_groups')
141 141
142 142 users_group_form = UserGroupForm()()
143 143 try:
144 144 form_result = users_group_form.to_python(dict(request.POST))
145 145 UserGroupModel().create(name=form_result['users_group_name'],
146 146 owner=self.rhodecode_user.user_id,
147 147 active=form_result['users_group_active'])
148 148
149 149 gr = form_result['users_group_name']
150 150 action_logger(self.rhodecode_user,
151 151 'admin_created_users_group:%s' % gr,
152 152 None, self.ip_addr, self.sa)
153 153 h.flash(_('Created user group %s') % gr, category='success')
154 154 Session().commit()
155 155 except formencode.Invalid, errors:
156 156 return htmlfill.render(
157 157 render('admin/users_groups/users_group_add.html'),
158 158 defaults=errors.value,
159 159 errors=errors.error_dict or {},
160 160 prefix_error=False,
161 161 encoding="UTF-8")
162 162 except Exception:
163 163 log.error(traceback.format_exc())
164 164 h.flash(_('Error occurred during creation of user group %s') \
165 165 % request.POST.get('users_group_name'), category='error')
166 166
167 167 return redirect(url('users_groups'))
168 168
169 169 @HasPermissionAllDecorator('hg.admin')
170 170 def new(self, format='html'):
171 171 """GET /users_groups/new: Form to create a new item"""
172 172 # url('new_users_group')
173 173 return render('admin/users_groups/users_group_add.html')
174 174
175 175 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
176 176 def update(self, id):
177 177 """PUT /users_groups/id: Update an existing item"""
178 178 # Forms posted to this method should contain a hidden field:
179 179 # <input type="hidden" name="_method" value="PUT" />
180 180 # Or using helpers:
181 181 # h.form(url('users_group', id=ID),
182 182 # method='put')
183 183 # url('users_group', id=ID)
184 184
185 185 c.users_group = UserGroup.get_or_404(id)
186 186 self.__load_data(id)
187 187
188 188 available_members = [safe_unicode(x[0]) for x in c.available_members]
189 189
190 190 users_group_form = UserGroupForm(edit=True,
191 191 old_data=c.users_group.get_dict(),
192 192 available_members=available_members)()
193 193
194 194 try:
195 195 form_result = users_group_form.to_python(request.POST)
196 196 UserGroupModel().update(c.users_group, form_result)
197 197 gr = form_result['users_group_name']
198 198 action_logger(self.rhodecode_user,
199 199 'admin_updated_users_group:%s' % gr,
200 200 None, self.ip_addr, self.sa)
201 201 h.flash(_('Updated user group %s') % gr, category='success')
202 202 Session().commit()
203 203 except formencode.Invalid, errors:
204 204 ug_model = UserGroupModel()
205 205 defaults = errors.value
206 206 e = errors.error_dict or {}
207 207 defaults.update({
208 208 'create_repo_perm': ug_model.has_perm(id,
209 209 'hg.create.repository'),
210 210 'fork_repo_perm': ug_model.has_perm(id,
211 211 'hg.fork.repository'),
212 212 '_method': 'put'
213 213 })
214 214
215 215 return htmlfill.render(
216 216 render('admin/users_groups/users_group_edit.html'),
217 217 defaults=defaults,
218 218 errors=e,
219 219 prefix_error=False,
220 220 encoding="UTF-8")
221 221 except Exception:
222 222 log.error(traceback.format_exc())
223 223 h.flash(_('Error occurred during update of user group %s') \
224 224 % request.POST.get('users_group_name'), category='error')
225 225
226 226 return redirect(url('edit_users_group', id=id))
227 227
228 228 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
229 229 def delete(self, id):
230 230 """DELETE /users_groups/id: Delete an existing item"""
231 231 # Forms posted to this method should contain a hidden field:
232 232 # <input type="hidden" name="_method" value="DELETE" />
233 233 # Or using helpers:
234 234 # h.form(url('users_group', id=ID),
235 235 # method='delete')
236 236 # url('users_group', id=ID)
237 237 usr_gr = UserGroup.get_or_404(id)
238 238 try:
239 239 UserGroupModel().delete(usr_gr)
240 240 Session().commit()
241 241 h.flash(_('Successfully deleted user group'), category='success')
242 242 except UserGroupsAssignedException, e:
243 243 h.flash(e, category='error')
244 244 except Exception:
245 245 log.error(traceback.format_exc())
246 246 h.flash(_('An error occurred during deletion of user group'),
247 247 category='error')
248 248 return redirect(url('users_groups'))
249 249
250 250 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
251 251 def set_user_group_perm_member(self, id):
252 252 """
253 253 grant permission for given usergroup
254 254
255 255 :param id:
256 256 """
257 257 user_group = UserGroup.get_or_404(id)
258 258 form = UserGroupPermsForm()().to_python(request.POST)
259 259
260 260 # set the permissions !
261 261 UserGroupModel()._update_permissions(user_group, form['perms_new'],
262 262 form['perms_updates'])
263 263 #TODO: implement this
264 264 #action_logger(self.rhodecode_user, 'admin_changed_repo_permissions',
265 265 # repo_name, self.ip_addr, self.sa)
266 266 Session().commit()
267 267 h.flash(_('User Group permissions updated'), category='success')
268 268 return redirect(url('edit_users_group', id=id))
269 269
270 270 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
271 271 def delete_user_group_perm_member(self, id):
272 272 """
273 273 DELETE an existing repository group permission user
274 274
275 275 :param group_name:
276 276 """
277 277 try:
278 278 obj_type = request.POST.get('obj_type')
279 279 obj_id = None
280 280 if obj_type == 'user':
281 281 obj_id = safe_int(request.POST.get('user_id'))
282 282 elif obj_type == 'user_group':
283 283 obj_id = safe_int(request.POST.get('user_group_id'))
284 284
285 285 if not c.rhodecode_user.is_admin:
286 286 if obj_type == 'user' and c.rhodecode_user.user_id == obj_id:
287 287 msg = _('Cannot revoke permission for yourself as admin')
288 288 h.flash(msg, category='warning')
289 289 raise Exception('revoke admin permission on self')
290 290 if obj_type == 'user':
291 291 UserGroupModel().revoke_user_permission(user_group=id,
292 292 user=obj_id)
293 293 elif obj_type == 'user_group':
294 294 pass
295 295 Session().commit()
296 296 except Exception:
297 297 log.error(traceback.format_exc())
298 h.flash(_('An error occurred during deletion of group user'),
298 h.flash(_('An error occurred during revoking of permission'),
299 299 category='error')
300 300 raise HTTPInternalServerError()
301 301
302 302 def show(self, id, format='html'):
303 303 """GET /users_groups/id: Show a specific item"""
304 304 # url('users_group', id=ID)
305 305
306 306 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
307 307 def edit(self, id, format='html'):
308 308 """GET /users_groups/id/edit: Form to edit an existing item"""
309 309 # url('edit_users_group', id=ID)
310 310
311 311 c.users_group = UserGroup.get_or_404(id)
312 312 self.__load_data(id)
313 313
314 314 defaults = self.__load_defaults(id)
315 315
316 316 return htmlfill.render(
317 317 render('admin/users_groups/users_group_edit.html'),
318 318 defaults=defaults,
319 319 encoding="UTF-8",
320 320 force_defaults=False
321 321 )
322 322
323 323 @HasUserGroupPermissionAnyDecorator('usergroup.admin')
324 324 def update_perm(self, id):
325 325 """PUT /users_perm/id: Update an existing item"""
326 326 # url('users_group_perm', id=ID, method='put')
327 327
328 328 users_group = UserGroup.get_or_404(id)
329 329 grant_create_perm = str2bool(request.POST.get('create_repo_perm'))
330 330 grant_fork_perm = str2bool(request.POST.get('fork_repo_perm'))
331 331 inherit_perms = str2bool(request.POST.get('inherit_default_permissions'))
332 332
333 333 usergroup_model = UserGroupModel()
334 334
335 335 try:
336 336 users_group.inherit_default_permissions = inherit_perms
337 337 Session().add(users_group)
338 338
339 339 if grant_create_perm:
340 340 usergroup_model.revoke_perm(id, 'hg.create.none')
341 341 usergroup_model.grant_perm(id, 'hg.create.repository')
342 342 h.flash(_("Granted 'repository create' permission to user group"),
343 343 category='success')
344 344 else:
345 345 usergroup_model.revoke_perm(id, 'hg.create.repository')
346 346 usergroup_model.grant_perm(id, 'hg.create.none')
347 347 h.flash(_("Revoked 'repository create' permission to user group"),
348 348 category='success')
349 349
350 350 if grant_fork_perm:
351 351 usergroup_model.revoke_perm(id, 'hg.fork.none')
352 352 usergroup_model.grant_perm(id, 'hg.fork.repository')
353 353 h.flash(_("Granted 'repository fork' permission to user group"),
354 354 category='success')
355 355 else:
356 356 usergroup_model.revoke_perm(id, 'hg.fork.repository')
357 357 usergroup_model.grant_perm(id, 'hg.fork.none')
358 358 h.flash(_("Revoked 'repository fork' permission to user group"),
359 359 category='success')
360 360
361 361 Session().commit()
362 362 except Exception:
363 363 log.error(traceback.format_exc())
364 364 h.flash(_('An error occurred during permissions saving'),
365 365 category='error')
366 366
367 367 return redirect(url('edit_users_group', id=id))
@@ -1,2195 +1,2229 b''
1 1 /**
2 2 RhodeCode JS Files
3 3 **/
4 4
5 5 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 6 console = { log: function() {} }
7 7 }
8 8
9 9
10 10 var str_repeat = function(i, m) {
11 11 for (var o = []; m > 0; o[--m] = i);
12 12 return o.join('');
13 13 };
14 14
15 15 /**
16 16 * INJECT .format function into String
17 17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
18 18 * Return "My name is Johny Bravo"
19 19 * Inspired by https://gist.github.com/1049426
20 20 */
21 21 String.prototype.format = function() {
22 22
23 23 function format() {
24 24 var str = this;
25 25 var len = arguments.length+1;
26 26 var safe = undefined;
27 27 var arg = undefined;
28 28
29 29 // For each {0} {1} {n...} replace with the argument in that position. If
30 30 // the argument is an object or an array it will be stringified to JSON.
31 31 for (var i=0; i < len; arg = arguments[i++]) {
32 32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
33 33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
34 34 }
35 35 return str;
36 36 }
37 37
38 38 // Save a reference of what may already exist under the property native.
39 39 // Allows for doing something like: if("".format.native) { /* use native */ }
40 40 format.native = String.prototype.format;
41 41
42 42 // Replace the prototype property
43 43 return format;
44 44
45 45 }();
46 46
47 47 String.prototype.strip = function(char) {
48 48 if(char === undefined){
49 49 char = '\\s';
50 50 }
51 51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
52 52 }
53 53 String.prototype.lstrip = function(char) {
54 54 if(char === undefined){
55 55 char = '\\s';
56 56 }
57 57 return this.replace(new RegExp('^'+char+'+'),'');
58 58 }
59 59 String.prototype.rstrip = function(char) {
60 60 if(char === undefined){
61 61 char = '\\s';
62 62 }
63 63 return this.replace(new RegExp(''+char+'+$'),'');
64 64 }
65 65
66 66
67 67 if(!Array.prototype.indexOf) {
68 68 Array.prototype.indexOf = function(needle) {
69 69 for(var i = 0; i < this.length; i++) {
70 70 if(this[i] === needle) {
71 71 return i;
72 72 }
73 73 }
74 74 return -1;
75 75 };
76 76 }
77 77
78 78 // IE(CRAP) doesn't support previousElementSibling
79 79 var prevElementSibling = function( el ) {
80 80 if( el.previousElementSibling ) {
81 81 return el.previousElementSibling;
82 82 } else {
83 83 while( el = el.previousSibling ) {
84 84 if( el.nodeType === 1 ) return el;
85 85 }
86 86 }
87 87 }
88 88
89 89 /**
90 90 * SmartColorGenerator
91 91 *
92 92 *usage::
93 93 * var CG = new ColorGenerator();
94 94 * var col = CG.getColor(key); //returns array of RGB
95 95 * 'rgb({0})'.format(col.join(',')
96 96 *
97 97 * @returns {ColorGenerator}
98 98 */
99 99 var ColorGenerator = function(){
100 100 this.GOLDEN_RATIO = 0.618033988749895;
101 101 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
102 102 this.HSV_1 = 0.75;//saturation
103 103 this.HSV_2 = 0.95;
104 104 this.color;
105 105 this.cacheColorMap = {};
106 106 };
107 107
108 108 ColorGenerator.prototype = {
109 109 getColor:function(key){
110 110 if(this.cacheColorMap[key] !== undefined){
111 111 return this.cacheColorMap[key];
112 112 }
113 113 else{
114 114 this.cacheColorMap[key] = this.generateColor();
115 115 return this.cacheColorMap[key];
116 116 }
117 117 },
118 118 _hsvToRgb:function(h,s,v){
119 119 if (s == 0.0)
120 120 return [v, v, v];
121 121 i = parseInt(h * 6.0)
122 122 f = (h * 6.0) - i
123 123 p = v * (1.0 - s)
124 124 q = v * (1.0 - s * f)
125 125 t = v * (1.0 - s * (1.0 - f))
126 126 i = i % 6
127 127 if (i == 0)
128 128 return [v, t, p]
129 129 if (i == 1)
130 130 return [q, v, p]
131 131 if (i == 2)
132 132 return [p, v, t]
133 133 if (i == 3)
134 134 return [p, q, v]
135 135 if (i == 4)
136 136 return [t, p, v]
137 137 if (i == 5)
138 138 return [v, p, q]
139 139 },
140 140 generateColor:function(){
141 141 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
142 142 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
143 143 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
144 144 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
145 145 function toRgb(v){
146 146 return ""+parseInt(v*256)
147 147 }
148 148 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
149 149
150 150 }
151 151 }
152 152
153 153 /**
154 154 * PyRoutesJS
155 155 *
156 156 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
157 157 */
158 158 var pyroutes = (function() {
159 159 // access global map defined in special file pyroutes
160 160 var matchlist = PROUTES_MAP;
161 161 var sprintf = (function() {
162 162 function get_type(variable) {
163 163 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
164 164 }
165 165 function str_repeat(input, multiplier) {
166 166 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
167 167 return output.join('');
168 168 }
169 169
170 170 var str_format = function() {
171 171 if (!str_format.cache.hasOwnProperty(arguments[0])) {
172 172 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
173 173 }
174 174 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
175 175 };
176 176
177 177 str_format.format = function(parse_tree, argv) {
178 178 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
179 179 for (i = 0; i < tree_length; i++) {
180 180 node_type = get_type(parse_tree[i]);
181 181 if (node_type === 'string') {
182 182 output.push(parse_tree[i]);
183 183 }
184 184 else if (node_type === 'array') {
185 185 match = parse_tree[i]; // convenience purposes only
186 186 if (match[2]) { // keyword argument
187 187 arg = argv[cursor];
188 188 for (k = 0; k < match[2].length; k++) {
189 189 if (!arg.hasOwnProperty(match[2][k])) {
190 190 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
191 191 }
192 192 arg = arg[match[2][k]];
193 193 }
194 194 }
195 195 else if (match[1]) { // positional argument (explicit)
196 196 arg = argv[match[1]];
197 197 }
198 198 else { // positional argument (implicit)
199 199 arg = argv[cursor++];
200 200 }
201 201
202 202 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
203 203 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
204 204 }
205 205 switch (match[8]) {
206 206 case 'b': arg = arg.toString(2); break;
207 207 case 'c': arg = String.fromCharCode(arg); break;
208 208 case 'd': arg = parseInt(arg, 10); break;
209 209 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
210 210 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
211 211 case 'o': arg = arg.toString(8); break;
212 212 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
213 213 case 'u': arg = Math.abs(arg); break;
214 214 case 'x': arg = arg.toString(16); break;
215 215 case 'X': arg = arg.toString(16).toUpperCase(); break;
216 216 }
217 217 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
218 218 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
219 219 pad_length = match[6] - String(arg).length;
220 220 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
221 221 output.push(match[5] ? arg + pad : pad + arg);
222 222 }
223 223 }
224 224 return output.join('');
225 225 };
226 226
227 227 str_format.cache = {};
228 228
229 229 str_format.parse = function(fmt) {
230 230 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
231 231 while (_fmt) {
232 232 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
233 233 parse_tree.push(match[0]);
234 234 }
235 235 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
236 236 parse_tree.push('%');
237 237 }
238 238 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
239 239 if (match[2]) {
240 240 arg_names |= 1;
241 241 var field_list = [], replacement_field = match[2], field_match = [];
242 242 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
243 243 field_list.push(field_match[1]);
244 244 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
245 245 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
246 246 field_list.push(field_match[1]);
247 247 }
248 248 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
249 249 field_list.push(field_match[1]);
250 250 }
251 251 else {
252 252 throw('[sprintf] huh?');
253 253 }
254 254 }
255 255 }
256 256 else {
257 257 throw('[sprintf] huh?');
258 258 }
259 259 match[2] = field_list;
260 260 }
261 261 else {
262 262 arg_names |= 2;
263 263 }
264 264 if (arg_names === 3) {
265 265 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
266 266 }
267 267 parse_tree.push(match);
268 268 }
269 269 else {
270 270 throw('[sprintf] huh?');
271 271 }
272 272 _fmt = _fmt.substring(match[0].length);
273 273 }
274 274 return parse_tree;
275 275 };
276 276
277 277 return str_format;
278 278 })();
279 279
280 280 var vsprintf = function(fmt, argv) {
281 281 argv.unshift(fmt);
282 282 return sprintf.apply(null, argv);
283 283 };
284 284 return {
285 285 'url': function(route_name, params) {
286 286 var result = route_name;
287 287 if (typeof(params) != 'object'){
288 288 params = {};
289 289 }
290 290 if (matchlist.hasOwnProperty(route_name)) {
291 291 var route = matchlist[route_name];
292 292 // param substitution
293 293 for(var i=0; i < route[1].length; i++) {
294 294
295 295 if (!params.hasOwnProperty(route[1][i]))
296 296 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
297 297 }
298 298 result = sprintf(route[0], params);
299 299
300 300 var ret = [];
301 301 //extra params => GET
302 302 for(param in params){
303 303 if (route[1].indexOf(param) == -1){
304 304 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
305 305 }
306 306 }
307 307 var _parts = ret.join("&");
308 308 if(_parts){
309 309 result = result +'?'+ _parts
310 310 }
311 311 }
312 312
313 313 return result;
314 314 },
315 315 'register': function(route_name, route_tmpl, req_params) {
316 316 if (typeof(req_params) != 'object') {
317 317 req_params = [];
318 318 }
319 319 //fix escape
320 320 route_tmpl = unescape(route_tmpl);
321 321 keys = [];
322 322 for (o in req_params){
323 323 keys.push(req_params[o])
324 324 }
325 325 matchlist[route_name] = [
326 326 route_tmpl,
327 327 keys
328 328 ]
329 329 },
330 330 '_routes': function(){
331 331 return matchlist;
332 332 }
333 333 }
334 334 })();
335 335
336 336
337 337
338 338 /**
339 339 * GLOBAL YUI Shortcuts
340 340 */
341 341 var YUC = YAHOO.util.Connect;
342 342 var YUD = YAHOO.util.Dom;
343 343 var YUE = YAHOO.util.Event;
344 344 var YUQ = YAHOO.util.Selector.query;
345 345
346 346 // defines if push state is enabled for this browser ?
347 347 var push_state_enabled = Boolean(
348 348 window.history && window.history.pushState && window.history.replaceState
349 349 && !( /* disable for versions of iOS before version 4.3 (8F190) */
350 350 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
351 351 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
352 352 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
353 353 )
354 354 );
355 355
356 356 var _run_callbacks = function(callbacks){
357 357 if (callbacks !== undefined){
358 358 var _l = callbacks.length;
359 359 for (var i=0;i<_l;i++){
360 360 var func = callbacks[i];
361 361 if(typeof(func)=='function'){
362 362 try{
363 363 func();
364 364 }catch (err){};
365 365 }
366 366 }
367 367 }
368 368 }
369 369
370 370 /**
371 * turns objects into GET query string
372 */
373 var toQueryString = function(o) {
374 if(typeof o !== 'object') {
375 return false;
376 }
377 var _p, _qs = [];
378 for(_p in o) {
379 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
380 }
381 return _qs.join('&');
382 };
383
384 /**
371 385 * Partial Ajax Implementation
372 386 *
373 387 * @param url: defines url to make partial request
374 388 * @param container: defines id of container to input partial result
375 389 * @param s_call: success callback function that takes o as arg
376 390 * o.tId
377 391 * o.status
378 392 * o.statusText
379 393 * o.getResponseHeader[ ]
380 394 * o.getAllResponseHeaders
381 395 * o.responseText
382 396 * o.responseXML
383 397 * o.argument
384 398 * @param f_call: failure callback
385 399 * @param args arguments
386 400 */
387 401 function ypjax(url,container,s_call,f_call,args){
388 402 var method='GET';
389 403 if(args===undefined){
390 404 args=null;
391 405 }
392 406
393 407 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
394 408 YUC.initHeader('X-PARTIAL-XHR',true);
395 409
396 410 // wrapper of passed callback
397 411 var s_wrapper = (function(o){
398 412 return function(o){
399 413 YUD.get(container).innerHTML=o.responseText;
400 414 YUD.setStyle(container,'opacity','1.0');
401 415 //execute the given original callback
402 416 if (s_call !== undefined){
403 417 s_call(o);
404 418 }
405 419 }
406 420 })()
407 421 YUD.setStyle(container,'opacity','0.3');
408 422 YUC.asyncRequest(method,url,{
409 423 success:s_wrapper,
410 424 failure:function(o){
411 425 console.log(o);
412 426 YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
413 427 YUD.setStyle(container,'opacity','1.0');
414 428 },
415 429 cache:false
416 430 },args);
417 431
418 432 };
419 433
420 434 var ajaxGET = function(url,success) {
421 435 // Set special header for ajax == HTTP_X_PARTIAL_XHR
422 436 YUC.initHeader('X-PARTIAL-XHR',true);
423 437
424 438 var sUrl = url;
425 439 var callback = {
426 440 success: success,
427 441 failure: function (o) {
428 442 if (o.status != 0) {
429 443 alert("error: " + o.statusText);
430 444 };
431 445 },
432 446 };
433 447
434 448 var request = YAHOO.util.Connect.asyncRequest('GET', sUrl, callback);
435 449 return request;
436 450 };
437 451
438 452
439 453
440 454 var ajaxPOST = function(url,postData,success) {
441 455 // Set special header for ajax == HTTP_X_PARTIAL_XHR
442 456 YUC.initHeader('X-PARTIAL-XHR',true);
443 457
444 var toQueryString = function(o) {
445 if(typeof o !== 'object') {
446 return false;
447 }
448 var _p, _qs = [];
449 for(_p in o) {
450 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
451 }
452 return _qs.join('&');
453 };
454
455 458 var sUrl = url;
456 459 var callback = {
457 460 success: success,
458 461 failure: function (o) {
459 462 alert("error");
460 463 },
461 464 };
462 465 var postData = toQueryString(postData);
463 466 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
464 467 return request;
465 468 };
466 469
467 470
468 471 /**
469 472 * tooltip activate
470 473 */
471 474 var tooltip_activate = function(){
472 475 yt = YAHOO.yuitip.main;
473 476 YUE.onDOMReady(yt.init);
474 477 };
475 478
476 479 /**
477 480 * show more
478 481 */
479 482 var show_more_event = function(){
480 483 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
481 484 var el = e.target;
482 485 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
483 486 YUD.setStyle(el.parentNode,'display','none');
484 487 });
485 488 };
486 489
487 490 /**
488 491 * show changeset tooltip
489 492 */
490 493 var show_changeset_tooltip = function(){
491 494 YUE.on(YUD.getElementsByClassName('lazy-cs'), 'mouseover', function(e){
492 495 var target = e.currentTarget;
493 496 var rid = YUD.getAttribute(target,'raw_id');
494 497 var repo_name = YUD.getAttribute(target,'repo_name');
495 498 var ttid = 'tt-'+rid;
496 499 var success = function(o){
497 500 var json = JSON.parse(o.responseText);
498 501 YUD.addClass(target,'tooltip')
499 502 YUD.setAttribute(target, 'title',json['message']);
500 503 YAHOO.yuitip.main.show_yuitip(e, target);
501 504 }
502 505 if(rid && !YUD.hasClass(target, 'tooltip')){
503 506 YUD.setAttribute(target,'id',ttid);
504 507 YUD.setAttribute(target, 'title',_TM['loading...']);
505 508 YAHOO.yuitip.main.set_listeners(target);
506 509 YAHOO.yuitip.main.show_yuitip(e, target);
507 510 var url = pyroutes.url('changeset_info', {"repo_name":repo_name, "revision": rid});
508 511 ajaxGET(url, success)
509 512 }
510 513 });
511 514 };
512 515
513 516 var onSuccessFollow = function(target){
514 517 var f = YUD.get(target);
515 518 var f_cnt = YUD.get('current_followers_count');
516 519
517 520 if(YUD.hasClass(f, 'follow')){
518 521 f.setAttribute('class','following');
519 522 f.setAttribute('title',_TM['Stop following this repository']);
520 523
521 524 if(f_cnt){
522 525 var cnt = Number(f_cnt.innerHTML)+1;
523 526 f_cnt.innerHTML = cnt;
524 527 }
525 528 }
526 529 else{
527 530 f.setAttribute('class','follow');
528 531 f.setAttribute('title',_TM['Start following this repository']);
529 532 if(f_cnt){
530 533 var cnt = Number(f_cnt.innerHTML)-1;
531 534 f_cnt.innerHTML = cnt;
532 535 }
533 536 }
534 537 }
535 538
536 539 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
537 540 args = 'follows_user_id='+fallows_user_id;
538 541 args+= '&amp;auth_token='+token;
539 542 if(user_id != undefined){
540 543 args+="&amp;user_id="+user_id;
541 544 }
542 545 YUC.asyncRequest('POST',TOGGLE_FOLLOW_URL,{
543 546 success:function(o){
544 547 onSuccessFollow(target);
545 548 }
546 549 },args);
547 550 return false;
548 551 }
549 552
550 553 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
551 554
552 555 args = 'follows_repo_id='+fallows_repo_id;
553 556 args+= '&amp;auth_token='+token;
554 557 if(user_id != undefined){
555 558 args+="&amp;user_id="+user_id;
556 559 }
557 560 YUC.asyncRequest('POST',TOGGLE_FOLLOW_URL,{
558 561 success:function(o){
559 562 onSuccessFollow(target);
560 563 }
561 564 },args);
562 565 return false;
563 566 }
564 567
565 568 var showRepoSize = function(target, repo_name, token){
566 569 var args= 'auth_token='+token;
567 570
568 571 if(!YUD.hasClass(target, 'loaded')){
569 572 YUD.get(target).innerHTML = _TM['Loading ...'];
570 573 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
571 574 YUC.asyncRequest('POST',url,{
572 575 success:function(o){
573 576 YUD.get(target).innerHTML = JSON.parse(o.responseText);
574 577 YUD.addClass(target, 'loaded');
575 578 }
576 579 },args);
577 580 }
578 581 return false;
579 582 }
580 583
581 584 /**
582 585 * TOOLTIP IMPL.
583 586 */
584 587 YAHOO.namespace('yuitip');
585 588 YAHOO.yuitip.main = {
586 589
587 590 $: YAHOO.util.Dom.get,
588 591
589 592 bgColor: '#000',
590 593 speed: 0.3,
591 594 opacity: 0.9,
592 595 offset: [15,15],
593 596 useAnim: false,
594 597 maxWidth: 600,
595 598 add_links: false,
596 599 yuitips: [],
597 600
598 601 set_listeners: function(tt){
599 602 YUE.on(tt, 'mouseover', yt.show_yuitip, tt);
600 603 YUE.on(tt, 'mousemove', yt.move_yuitip, tt);
601 604 YUE.on(tt, 'mouseout', yt.close_yuitip, tt);
602 605 },
603 606
604 607 init: function(){
605 608 yt.tipBox = yt.$('tip-box');
606 609 if(!yt.tipBox){
607 610 yt.tipBox = document.createElement('div');
608 611 document.body.appendChild(yt.tipBox);
609 612 yt.tipBox.id = 'tip-box';
610 613 }
611 614
612 615 YUD.setStyle(yt.tipBox, 'display', 'none');
613 616 YUD.setStyle(yt.tipBox, 'position', 'absolute');
614 617 if(yt.maxWidth !== null){
615 618 YUD.setStyle(yt.tipBox, 'max-width', yt.maxWidth+'px');
616 619 }
617 620
618 621 var yuitips = YUD.getElementsByClassName('tooltip');
619 622
620 623 if(yt.add_links === true){
621 624 var links = document.getElementsByTagName('a');
622 625 var linkLen = links.length;
623 626 for(i=0;i<linkLen;i++){
624 627 yuitips.push(links[i]);
625 628 }
626 629 }
627 630
628 631 var yuiLen = yuitips.length;
629 632
630 633 for(i=0;i<yuiLen;i++){
631 634 yt.set_listeners(yuitips[i]);
632 635 }
633 636 },
634 637
635 638 show_yuitip: function(e, el){
636 639 YUE.stopEvent(e);
637 640 if(el.tagName.toLowerCase() === 'img'){
638 641 yt.tipText = el.alt ? el.alt : '';
639 642 } else {
640 643 yt.tipText = el.title ? el.title : '';
641 644 }
642 645
643 646 if(yt.tipText !== ''){
644 647 // save org title
645 648 YUD.setAttribute(el, 'tt_title', yt.tipText);
646 649 // reset title to not show org tooltips
647 650 YUD.setAttribute(el, 'title', '');
648 651
649 652 yt.tipBox.innerHTML = yt.tipText;
650 653 YUD.setStyle(yt.tipBox, 'display', 'block');
651 654 if(yt.useAnim === true){
652 655 YUD.setStyle(yt.tipBox, 'opacity', '0');
653 656 var newAnim = new YAHOO.util.Anim(yt.tipBox,
654 657 {
655 658 opacity: { to: yt.opacity }
656 659 }, yt.speed, YAHOO.util.Easing.easeOut
657 660 );
658 661 newAnim.animate();
659 662 }
660 663 }
661 664 },
662 665
663 666 move_yuitip: function(e, el){
664 667 YUE.stopEvent(e);
665 668 var movePos = YUE.getXY(e);
666 669 YUD.setStyle(yt.tipBox, 'top', (movePos[1] + yt.offset[1]) + 'px');
667 670 YUD.setStyle(yt.tipBox, 'left', (movePos[0] + yt.offset[0]) + 'px');
668 671 },
669 672
670 673 close_yuitip: function(e, el){
671 674 YUE.stopEvent(e);
672 675
673 676 if(yt.useAnim === true){
674 677 var newAnim = new YAHOO.util.Anim(yt.tipBox,
675 678 {
676 679 opacity: { to: 0 }
677 680 }, yt.speed, YAHOO.util.Easing.easeOut
678 681 );
679 682 newAnim.animate();
680 683 } else {
681 684 YUD.setStyle(yt.tipBox, 'display', 'none');
682 685 }
683 686 YUD.setAttribute(el,'title', YUD.getAttribute(el, 'tt_title'));
684 687 }
685 688 }
686 689
687 690 /**
688 691 * Quick filter widget
689 692 *
690 693 * @param target: filter input target
691 694 * @param nodes: list of nodes in html we want to filter.
692 695 * @param display_element function that takes current node from nodes and
693 696 * does hide or show based on the node
694 697 *
695 698 */
696 699 var q_filter = function(target,nodes,display_element){
697 700
698 701 var nodes = nodes;
699 702 var q_filter_field = YUD.get(target);
700 703 var F = YAHOO.namespace(target);
701 704
702 705 YUE.on(q_filter_field,'keyup',function(e){
703 706 clearTimeout(F.filterTimeout);
704 707 F.filterTimeout = setTimeout(F.updateFilter,600);
705 708 });
706 709
707 710 F.filterTimeout = null;
708 711
709 712 var show_node = function(node){
710 713 YUD.setStyle(node,'display','')
711 714 }
712 715 var hide_node = function(node){
713 716 YUD.setStyle(node,'display','none');
714 717 }
715 718
716 719 F.updateFilter = function() {
717 720 // Reset timeout
718 721 F.filterTimeout = null;
719 722
720 723 var obsolete = [];
721 724
722 725 var req = q_filter_field.value.toLowerCase();
723 726
724 727 var l = nodes.length;
725 728 var i;
726 729 var showing = 0;
727 730
728 731 for (i=0;i<l;i++ ){
729 732 var n = nodes[i];
730 733 var target_element = display_element(n)
731 734 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
732 735 hide_node(target_element);
733 736 }
734 737 else{
735 738 show_node(target_element);
736 739 showing+=1;
737 740 }
738 741 }
739 742
740 743 // if repo_count is set update the number
741 744 var cnt = YUD.get('repo_count');
742 745 if(cnt){
743 746 YUD.get('repo_count').innerHTML = showing;
744 747 }
745 748
746 749 }
747 750 };
748 751
749 752 var tableTr = function(cls, body){
750 753 var _el = document.createElement('div');
751 754 var cont = new YAHOO.util.Element(body);
752 755 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
753 756 var id = 'comment-tr-{0}'.format(comment_id);
754 757 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
755 758 '<td class="lineno-inline new-inline"></td>'+
756 759 '<td class="lineno-inline old-inline"></td>'+
757 760 '<td>{2}</td>'+
758 761 '</tr></tbody></table>').format(id, cls, body);
759 762 _el.innerHTML = _html;
760 763 return _el.children[0].children[0].children[0];
761 764 };
762 765
763 766 /** comments **/
764 767 var removeInlineForm = function(form) {
765 768 form.parentNode.removeChild(form);
766 769 };
767 770
768 771 var createInlineForm = function(parent_tr, f_path, line) {
769 772 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
770 773 tmpl = tmpl.format(f_path, line);
771 774 var form = tableTr('comment-form-inline',tmpl)
772 775
773 776 // create event for hide button
774 777 form = new YAHOO.util.Element(form);
775 778 var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]);
776 779 form_hide_button.on('click', function(e) {
777 780 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
778 781 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
779 782 YUD.setStyle(newtr.nextElementSibling,'display','');
780 783 }
781 784 removeInlineForm(newtr);
782 785 YUD.removeClass(parent_tr, 'form-open');
783 786 YUD.removeClass(parent_tr, 'hl-comment');
784 787
785 788 });
786 789
787 790 return form
788 791 };
789 792
790 793 /**
791 794 * Inject inline comment for on given TR this tr should be always an .line
792 795 * tr containing the line. Code will detect comment, and always put the comment
793 796 * block at the very bottom
794 797 */
795 798 var injectInlineForm = function(tr){
796 799 if(!YUD.hasClass(tr, 'line')){
797 800 return
798 801 }
799 802 var submit_url = AJAX_COMMENT_URL;
800 803 var _td = YUD.getElementsByClassName('code',null,tr)[0];
801 804 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
802 805 return
803 806 }
804 807 YUD.addClass(tr,'form-open');
805 808 YUD.addClass(tr,'hl-comment');
806 809 var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0];
807 810 var f_path = YUD.getAttribute(node,'path');
808 811 var lineno = getLineNo(tr);
809 812 var form = createInlineForm(tr, f_path, lineno, submit_url);
810 813
811 814 var parent = tr;
812 815 while (1){
813 816 var n = parent.nextElementSibling;
814 817 // next element are comments !
815 818 if(YUD.hasClass(n,'inline-comments')){
816 819 parent = n;
817 820 }
818 821 else{
819 822 break;
820 823 }
821 824 }
822 825 YUD.insertAfter(form,parent);
823 826 var f = YUD.get(form);
824 827 var overlay = YUD.getElementsByClassName('overlay',null,f)[0];
825 828 var _form = YUD.getElementsByClassName('inline-form',null,f)[0];
826 829
827 830 YUE.on(YUD.get(_form), 'submit',function(e){
828 831 YUE.preventDefault(e);
829 832
830 833 //ajax submit
831 834 var text = YUD.get('text_'+lineno).value;
832 835 var postData = {
833 836 'text':text,
834 837 'f_path':f_path,
835 838 'line':lineno
836 839 };
837 840
838 841 if(lineno === undefined){
839 842 alert('missing line !');
840 843 return
841 844 }
842 845 if(f_path === undefined){
843 846 alert('missing file path !');
844 847 return
845 848 }
846 849
847 850 if(text == ""){
848 851 return
849 852 }
850 853
851 854 var success = function(o){
852 855 YUD.removeClass(tr, 'form-open');
853 856 removeInlineForm(f);
854 857 var json_data = JSON.parse(o.responseText);
855 858 renderInlineComment(json_data);
856 859 };
857 860
858 861 if (YUD.hasClass(overlay,'overlay')){
859 862 var w = _form.offsetWidth;
860 863 var h = _form.offsetHeight;
861 864 YUD.setStyle(overlay,'width',w+'px');
862 865 YUD.setStyle(overlay,'height',h+'px');
863 866 }
864 867 YUD.addClass(overlay, 'submitting');
865 868
866 869 ajaxPOST(submit_url, postData, success);
867 870 });
868 871
869 872 YUE.on('preview-btn_'+lineno, 'click', function(e){
870 873 var _text = YUD.get('text_'+lineno).value;
871 874 if(!_text){
872 875 return
873 876 }
874 877 var post_data = {'text': _text};
875 878 YUD.addClass('preview-box_'+lineno, 'unloaded');
876 879 YUD.get('preview-box_'+lineno).innerHTML = _TM['Loading ...'];
877 880 YUD.setStyle('edit-container_'+lineno, 'display', 'none');
878 881 YUD.setStyle('preview-container_'+lineno, 'display', '');
879 882
880 883 var url = pyroutes.url('changeset_comment_preview', {'repo_name': REPO_NAME});
881 884 ajaxPOST(url,post_data,function(o){
882 885 YUD.get('preview-box_'+lineno).innerHTML = o.responseText;
883 886 YUD.removeClass('preview-box_'+lineno, 'unloaded');
884 887 })
885 888 })
886 889 YUE.on('edit-btn_'+lineno, 'click', function(e){
887 890 YUD.setStyle('edit-container_'+lineno, 'display', '');
888 891 YUD.setStyle('preview-container_'+lineno, 'display', 'none');
889 892 })
890 893
891 894
892 895 setTimeout(function(){
893 896 // callbacks
894 897 tooltip_activate();
895 898 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
896 899 _USERS_AC_DATA, _GROUPS_AC_DATA);
897 900 var _e = YUD.get('text_'+lineno);
898 901 if(_e){
899 902 _e.focus();
900 903 }
901 904 },10)
902 905 };
903 906
904 907 var deleteComment = function(comment_id){
905 908 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
906 909 var postData = {'_method':'delete'};
907 910 var success = function(o){
908 911 var n = YUD.get('comment-tr-'+comment_id);
909 912 var root = prevElementSibling(prevElementSibling(n));
910 913 n.parentNode.removeChild(n);
911 914
912 915 // scann nodes, and attach add button to last one only for TR
913 916 // which are the inline comments
914 917 if(root && root.tagName == 'TR'){
915 918 placeAddButton(root);
916 919 }
917 920 }
918 921 ajaxPOST(url,postData,success);
919 922 }
920 923
921 924 var createInlineAddButton = function(tr){
922 925
923 926 var label = TRANSLATION_MAP['Add another comment'];
924 927
925 928 var html_el = document.createElement('div');
926 929 YUD.addClass(html_el, 'add-comment');
927 930 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
928 931
929 932 var add = new YAHOO.util.Element(html_el);
930 933 add.on('click', function(e) {
931 934 injectInlineForm(tr);
932 935 });
933 936 return add;
934 937 };
935 938
936 939 var getLineNo = function(tr) {
937 940 var line;
938 941 var o = tr.children[0].id.split('_');
939 942 var n = tr.children[1].id.split('_');
940 943
941 944 if (n.length >= 2) {
942 945 line = n[n.length-1];
943 946 } else if (o.length >= 2) {
944 947 line = o[o.length-1];
945 948 }
946 949
947 950 return line
948 951 };
949 952
950 953 var placeAddButton = function(target_tr){
951 954 if(!target_tr){
952 955 return
953 956 }
954 957 var last_node = target_tr;
955 958 //scann
956 959 while (1){
957 960 var n = last_node.nextElementSibling;
958 961 // next element are comments !
959 962 if(YUD.hasClass(n,'inline-comments')){
960 963 last_node = n;
961 964 //also remove the comment button from previous
962 965 var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node);
963 966 for(var i=0;i<comment_add_buttons.length;i++){
964 967 var b = comment_add_buttons[i];
965 968 b.parentNode.removeChild(b);
966 969 }
967 970 }
968 971 else{
969 972 break;
970 973 }
971 974 }
972 975
973 976 var add = createInlineAddButton(target_tr);
974 977 // get the comment div
975 978 var comment_block = YUD.getElementsByClassName('comment',null,last_node)[0];
976 979 // attach add button
977 980 YUD.insertAfter(add,comment_block);
978 981 }
979 982
980 983 /**
981 984 * Places the inline comment into the changeset block in proper line position
982 985 */
983 986 var placeInline = function(target_container,lineno,html){
984 987 var lineid = "{0}_{1}".format(target_container,lineno);
985 988 var target_line = YUD.get(lineid);
986 989 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
987 990
988 991 // check if there are comments already !
989 992 var parent = target_line.parentNode;
990 993 var root_parent = parent;
991 994 while (1){
992 995 var n = parent.nextElementSibling;
993 996 // next element are comments !
994 997 if(YUD.hasClass(n,'inline-comments')){
995 998 parent = n;
996 999 }
997 1000 else{
998 1001 break;
999 1002 }
1000 1003 }
1001 1004 // put in the comment at the bottom
1002 1005 YUD.insertAfter(comment,parent);
1003 1006
1004 1007 // scann nodes, and attach add button to last one
1005 1008 placeAddButton(root_parent);
1006 1009
1007 1010 return target_line;
1008 1011 }
1009 1012
1010 1013 /**
1011 1014 * make a single inline comment and place it inside
1012 1015 */
1013 1016 var renderInlineComment = function(json_data){
1014 1017 try{
1015 1018 var html = json_data['rendered_text'];
1016 1019 var lineno = json_data['line_no'];
1017 1020 var target_id = json_data['target_id'];
1018 1021 placeInline(target_id, lineno, html);
1019 1022
1020 1023 }catch(e){
1021 1024 console.log(e);
1022 1025 }
1023 1026 }
1024 1027
1025 1028 /**
1026 1029 * Iterates over all the inlines, and places them inside proper blocks of data
1027 1030 */
1028 1031 var renderInlineComments = function(file_comments){
1029 1032 for (f in file_comments){
1030 1033 // holding all comments for a FILE
1031 1034 var box = file_comments[f];
1032 1035
1033 1036 var target_id = YUD.getAttribute(box,'target_id');
1034 1037 // actually comments with line numbers
1035 1038 var comments = box.children;
1036 1039 for(var i=0; i<comments.length; i++){
1037 1040 var data = {
1038 1041 'rendered_text': comments[i].outerHTML,
1039 1042 'line_no': YUD.getAttribute(comments[i],'line'),
1040 1043 'target_id': target_id
1041 1044 }
1042 1045 renderInlineComment(data);
1043 1046 }
1044 1047 }
1045 1048 }
1046 1049
1047 1050 var fileBrowserListeners = function(current_url, node_list_url, url_base){
1048 1051 var current_url_branch = +"?branch=__BRANCH__";
1049 1052
1050 1053 YUE.on('stay_at_branch','click',function(e){
1051 1054 if(e.target.checked){
1052 1055 var uri = current_url_branch;
1053 1056 uri = uri.replace('__BRANCH__',e.target.value);
1054 1057 window.location = uri;
1055 1058 }
1056 1059 else{
1057 1060 window.location = current_url;
1058 1061 }
1059 1062 })
1060 1063
1061 1064 var n_filter = YUD.get('node_filter');
1062 1065 var F = YAHOO.namespace('node_filter');
1063 1066
1064 1067 F.filterTimeout = null;
1065 1068 var nodes = null;
1066 1069
1067 1070 F.initFilter = function(){
1068 1071 YUD.setStyle('node_filter_box_loading','display','');
1069 1072 YUD.setStyle('search_activate_id','display','none');
1070 1073 YUD.setStyle('add_node_id','display','none');
1071 1074 YUC.initHeader('X-PARTIAL-XHR',true);
1072 1075 YUC.asyncRequest('GET', node_list_url, {
1073 1076 success:function(o){
1074 1077 nodes = JSON.parse(o.responseText).nodes;
1075 1078 YUD.setStyle('node_filter_box_loading','display','none');
1076 1079 YUD.setStyle('node_filter_box','display','');
1077 1080 n_filter.focus();
1078 1081 if(YUD.hasClass(n_filter,'init')){
1079 1082 n_filter.value = '';
1080 1083 YUD.removeClass(n_filter,'init');
1081 1084 }
1082 1085 },
1083 1086 failure:function(o){
1084 1087 console.log('failed to load');
1085 1088 }
1086 1089 },null);
1087 1090 }
1088 1091
1089 1092 F.updateFilter = function(e) {
1090 1093
1091 1094 return function(){
1092 1095 // Reset timeout
1093 1096 F.filterTimeout = null;
1094 1097 var query = e.target.value.toLowerCase();
1095 1098 var match = [];
1096 1099 var matches = 0;
1097 1100 var matches_max = 20;
1098 1101 if (query != ""){
1099 1102 for(var i=0;i<nodes.length;i++){
1100 1103
1101 1104 var pos = nodes[i].name.toLowerCase().indexOf(query)
1102 1105 if(query && pos != -1){
1103 1106
1104 1107 matches++
1105 1108 //show only certain amount to not kill browser
1106 1109 if (matches > matches_max){
1107 1110 break;
1108 1111 }
1109 1112
1110 1113 var n = nodes[i].name;
1111 1114 var t = nodes[i].type;
1112 1115 var n_hl = n.substring(0,pos)
1113 1116 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
1114 1117 +n.substring(pos+query.length)
1115 1118 var new_url = url_base.replace('__FPATH__',n);
1116 1119 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
1117 1120 }
1118 1121 if(match.length >= matches_max){
1119 1122 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
1120 1123 }
1121 1124 }
1122 1125 }
1123 1126 if(query != ""){
1124 1127 YUD.setStyle('tbody','display','none');
1125 1128 YUD.setStyle('tbody_filtered','display','');
1126 1129
1127 1130 if (match.length==0){
1128 1131 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
1129 1132 }
1130 1133
1131 1134 YUD.get('tbody_filtered').innerHTML = match.join("");
1132 1135 }
1133 1136 else{
1134 1137 YUD.setStyle('tbody','display','');
1135 1138 YUD.setStyle('tbody_filtered','display','none');
1136 1139 }
1137 1140
1138 1141 }
1139 1142 };
1140 1143
1141 1144 YUE.on(YUD.get('filter_activate'),'click',function(){
1142 1145 F.initFilter();
1143 1146 })
1144 1147 YUE.on(n_filter,'click',function(){
1145 1148 if(YUD.hasClass(n_filter,'init')){
1146 1149 n_filter.value = '';
1147 1150 YUD.removeClass(n_filter,'init');
1148 1151 }
1149 1152 });
1150 1153 YUE.on(n_filter,'keyup',function(e){
1151 1154 clearTimeout(F.filterTimeout);
1152 1155 F.filterTimeout = setTimeout(F.updateFilter(e),600);
1153 1156 });
1154 1157 };
1155 1158
1156 1159
1157 1160 var initCodeMirror = function(textAreadId,resetUrl){
1158 1161 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
1159 1162 mode: "null",
1160 1163 lineNumbers:true
1161 1164 });
1162 1165 YUE.on('reset','click',function(e){
1163 1166 window.location=resetUrl
1164 1167 });
1165 1168
1166 1169 YUE.on('file_enable','click',function(){
1167 1170 YUD.setStyle('editor_container','display','');
1168 1171 YUD.setStyle('upload_file_container','display','none');
1169 1172 YUD.setStyle('filename_container','display','');
1170 1173 });
1171 1174
1172 1175 YUE.on('upload_file_enable','click',function(){
1173 1176 YUD.setStyle('editor_container','display','none');
1174 1177 YUD.setStyle('upload_file_container','display','');
1175 1178 YUD.setStyle('filename_container','display','none');
1176 1179 });
1177 1180 };
1178 1181
1179 1182
1180 1183
1181 1184 var getIdentNode = function(n){
1182 1185 //iterate thru nodes untill matched interesting node !
1183 1186
1184 1187 if (typeof n == 'undefined'){
1185 1188 return -1
1186 1189 }
1187 1190
1188 1191 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
1189 1192 return n
1190 1193 }
1191 1194 else{
1192 1195 return getIdentNode(n.parentNode);
1193 1196 }
1194 1197 };
1195 1198
1196 1199 var getSelectionLink = function(e) {
1197 1200
1198 1201 //get selection from start/to nodes
1199 1202 if (typeof window.getSelection != "undefined") {
1200 1203 s = window.getSelection();
1201 1204
1202 1205 from = getIdentNode(s.anchorNode);
1203 1206 till = getIdentNode(s.focusNode);
1204 1207
1205 1208 f_int = parseInt(from.id.replace('L',''));
1206 1209 t_int = parseInt(till.id.replace('L',''));
1207 1210
1208 1211 if (f_int > t_int){
1209 1212 //highlight from bottom
1210 1213 offset = -35;
1211 1214 ranges = [t_int,f_int];
1212 1215
1213 1216 }
1214 1217 else{
1215 1218 //highligth from top
1216 1219 offset = 35;
1217 1220 ranges = [f_int,t_int];
1218 1221 }
1219 1222 // if we select more than 2 lines
1220 1223 if (ranges[0] != ranges[1]){
1221 1224 if(YUD.get('linktt') == null){
1222 1225 hl_div = document.createElement('div');
1223 1226 hl_div.id = 'linktt';
1224 1227 }
1225 1228 hl_div.innerHTML = '';
1226 1229
1227 1230 anchor = '#L'+ranges[0]+'-'+ranges[1];
1228 1231 var link = document.createElement('a');
1229 1232 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
1230 1233 link.innerHTML = _TM['Selection link'];
1231 1234 hl_div.appendChild(link);
1232 1235 YUD.get('body').appendChild(hl_div);
1233 1236
1234 1237 xy = YUD.getXY(till.id);
1235 1238
1236 1239 YUD.addClass('linktt', 'hl-tip-box');
1237 1240 YUD.setStyle('linktt','top',xy[1]+offset+'px');
1238 1241 YUD.setStyle('linktt','left',xy[0]+'px');
1239 1242 YUD.setStyle('linktt','visibility','visible');
1240 1243
1241 1244 }
1242 1245 else{
1243 1246 YUD.setStyle('linktt','visibility','hidden');
1244 1247 }
1245 1248 }
1246 1249 };
1247 1250
1248 1251 var deleteNotification = function(url, notification_id,callbacks){
1249 1252 var callback = {
1250 1253 success:function(o){
1251 1254 var obj = YUD.get(String("notification_"+notification_id));
1252 1255 if(obj.parentNode !== undefined){
1253 1256 obj.parentNode.removeChild(obj);
1254 1257 }
1255 1258 _run_callbacks(callbacks);
1256 1259 },
1257 1260 failure:function(o){
1258 1261 alert("error");
1259 1262 },
1260 1263 };
1261 1264 var postData = '_method=delete';
1262 1265 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1263 1266 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
1264 1267 callback, postData);
1265 1268 };
1266 1269
1267 1270 var readNotification = function(url, notification_id,callbacks){
1268 1271 var callback = {
1269 1272 success:function(o){
1270 1273 var obj = YUD.get(String("notification_"+notification_id));
1271 1274 YUD.removeClass(obj, 'unread');
1272 1275 var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
1273 1276
1274 1277 if(r_button.parentNode !== undefined){
1275 1278 r_button.parentNode.removeChild(r_button);
1276 1279 }
1277 1280 _run_callbacks(callbacks);
1278 1281 },
1279 1282 failure:function(o){
1280 1283 alert("error");
1281 1284 },
1282 1285 };
1283 1286 var postData = '_method=put';
1284 1287 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1285 1288 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
1286 1289 callback, postData);
1287 1290 };
1288 1291
1289 1292 /** MEMBERS AUTOCOMPLETE WIDGET **/
1290 1293
1291 1294 var MembersAutoComplete = function (divid, cont, users_list, groups_list) {
1292 1295 var myUsers = users_list;
1293 1296 var myGroups = groups_list;
1294 1297
1295 1298 // Define a custom search function for the DataSource of users
1296 1299 var matchUsers = function (sQuery) {
1297 1300 // Case insensitive matching
1298 1301 var query = sQuery.toLowerCase();
1299 1302 var i = 0;
1300 1303 var l = myUsers.length;
1301 1304 var matches = [];
1302 1305
1303 1306 // Match against each name of each contact
1304 1307 for (; i < l; i++) {
1305 1308 contact = myUsers[i];
1306 1309 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1307 1310 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1308 1311 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1309 1312 matches[matches.length] = contact;
1310 1313 }
1311 1314 }
1312 1315 return matches;
1313 1316 };
1314 1317
1315 1318 // Define a custom search function for the DataSource of userGroups
1316 1319 var matchGroups = function (sQuery) {
1317 1320 // Case insensitive matching
1318 1321 var query = sQuery.toLowerCase();
1319 1322 var i = 0;
1320 1323 var l = myGroups.length;
1321 1324 var matches = [];
1322 1325
1323 1326 // Match against each name of each contact
1324 1327 for (; i < l; i++) {
1325 1328 matched_group = myGroups[i];
1326 1329 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1327 1330 matches[matches.length] = matched_group;
1328 1331 }
1329 1332 }
1330 1333 return matches;
1331 1334 };
1332 1335
1333 1336 //match all
1334 1337 var matchAll = function (sQuery) {
1335 1338 u = matchUsers(sQuery);
1336 1339 g = matchGroups(sQuery);
1337 1340 return u.concat(g);
1338 1341 };
1339 1342
1340 1343 // DataScheme for members
1341 1344 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
1342 1345 memberDS.responseSchema = {
1343 1346 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
1344 1347 };
1345 1348
1346 1349 // DataScheme for owner
1347 1350 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1348 1351 ownerDS.responseSchema = {
1349 1352 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1350 1353 };
1351 1354
1352 1355 // Instantiate AutoComplete for perms
1353 1356 var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
1354 1357 membersAC.useShadow = false;
1355 1358 membersAC.resultTypeList = false;
1356 1359 membersAC.animVert = false;
1357 1360 membersAC.animHoriz = false;
1358 1361 membersAC.animSpeed = 0.1;
1359 1362
1360 1363 // Instantiate AutoComplete for owner
1361 1364 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
1362 1365 ownerAC.useShadow = false;
1363 1366 ownerAC.resultTypeList = false;
1364 1367 ownerAC.animVert = false;
1365 1368 ownerAC.animHoriz = false;
1366 1369 ownerAC.animSpeed = 0.1;
1367 1370
1368 1371 // Helper highlight function for the formatter
1369 1372 var highlightMatch = function (full, snippet, matchindex) {
1370 1373 return full.substring(0, matchindex)
1371 1374 + "<span class='match'>"
1372 1375 + full.substr(matchindex, snippet.length)
1373 1376 + "</span>" + full.substring(matchindex + snippet.length);
1374 1377 };
1375 1378
1376 1379 // Custom formatter to highlight the matching letters
1377 1380 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
1378 1381 var query = sQuery.toLowerCase();
1379 1382 var _gravatar = function(res, em, group){
1380 1383 if (group !== undefined){
1381 1384 em = '/images/icons/group.png'
1382 1385 }
1383 1386 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1384 1387 return tmpl.format(em,res)
1385 1388 }
1386 1389 // group
1387 1390 if (oResultData.grname != undefined) {
1388 1391 var grname = oResultData.grname;
1389 1392 var grmembers = oResultData.grmembers;
1390 1393 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1391 1394 var grprefix = "{0}: ".format(_TM['Group']);
1392 1395 var grsuffix = " (" + grmembers + " )";
1393 1396 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1394 1397
1395 1398 if (grnameMatchIndex > -1) {
1396 1399 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
1397 1400 }
1398 1401 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
1399 1402 // Users
1400 1403 } else if (oResultData.nname != undefined) {
1401 1404 var fname = oResultData.fname || "";
1402 1405 var lname = oResultData.lname || "";
1403 1406 var nname = oResultData.nname;
1404 1407
1405 1408 // Guard against null value
1406 1409 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1407 1410 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1408 1411 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1409 1412 displayfname, displaylname, displaynname;
1410 1413
1411 1414 if (fnameMatchIndex > -1) {
1412 1415 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1413 1416 } else {
1414 1417 displayfname = fname;
1415 1418 }
1416 1419
1417 1420 if (lnameMatchIndex > -1) {
1418 1421 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1419 1422 } else {
1420 1423 displaylname = lname;
1421 1424 }
1422 1425
1423 1426 if (nnameMatchIndex > -1) {
1424 1427 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1425 1428 } else {
1426 1429 displaynname = nname ? "(" + nname + ")" : "";
1427 1430 }
1428 1431
1429 1432 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1430 1433 } else {
1431 1434 return '';
1432 1435 }
1433 1436 };
1434 1437 membersAC.formatResult = custom_formatter;
1435 1438 ownerAC.formatResult = custom_formatter;
1436 1439
1437 1440 var myHandler = function (sType, aArgs) {
1438 1441 var nextId = divid.split('perm_new_member_name_')[1];
1439 1442 var myAC = aArgs[0]; // reference back to the AC instance
1440 1443 var elLI = aArgs[1]; // reference to the selected LI element
1441 1444 var oData = aArgs[2]; // object literal of selected item's result data
1442 1445 //fill the autocomplete with value
1443 1446 if (oData.nname != undefined) {
1444 1447 //users
1445 1448 myAC.getInputEl().value = oData.nname;
1446 1449 YUD.get('perm_new_member_type_'+nextId).value = 'user';
1447 1450 } else {
1448 1451 //groups
1449 1452 myAC.getInputEl().value = oData.grname;
1450 1453 YUD.get('perm_new_member_type_'+nextId).value = 'users_group';
1451 1454 }
1452 1455 };
1453 1456
1454 1457 membersAC.itemSelectEvent.subscribe(myHandler);
1455 1458 if(ownerAC.itemSelectEvent){
1456 1459 ownerAC.itemSelectEvent.subscribe(myHandler);
1457 1460 }
1458 1461
1459 1462 return {
1460 1463 memberDS: memberDS,
1461 1464 ownerDS: ownerDS,
1462 1465 membersAC: membersAC,
1463 1466 ownerAC: ownerAC,
1464 1467 };
1465 1468 }
1466 1469
1467 1470
1468 1471 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1469 1472 var myUsers = users_list;
1470 1473 var myGroups = groups_list;
1471 1474
1472 1475 // Define a custom search function for the DataSource of users
1473 1476 var matchUsers = function (sQuery) {
1474 1477 var org_sQuery = sQuery;
1475 1478 if(this.mentionQuery == null){
1476 1479 return []
1477 1480 }
1478 1481 sQuery = this.mentionQuery;
1479 1482 // Case insensitive matching
1480 1483 var query = sQuery.toLowerCase();
1481 1484 var i = 0;
1482 1485 var l = myUsers.length;
1483 1486 var matches = [];
1484 1487
1485 1488 // Match against each name of each contact
1486 1489 for (; i < l; i++) {
1487 1490 contact = myUsers[i];
1488 1491 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1489 1492 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1490 1493 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1491 1494 matches[matches.length] = contact;
1492 1495 }
1493 1496 }
1494 1497 return matches
1495 1498 };
1496 1499
1497 1500 //match all
1498 1501 var matchAll = function (sQuery) {
1499 1502 u = matchUsers(sQuery);
1500 1503 return u
1501 1504 };
1502 1505
1503 1506 // DataScheme for owner
1504 1507 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1505 1508
1506 1509 ownerDS.responseSchema = {
1507 1510 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1508 1511 };
1509 1512
1510 1513 // Instantiate AutoComplete for mentions
1511 1514 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1512 1515 ownerAC.useShadow = false;
1513 1516 ownerAC.resultTypeList = false;
1514 1517 ownerAC.suppressInputUpdate = true;
1515 1518 ownerAC.animVert = false;
1516 1519 ownerAC.animHoriz = false;
1517 1520 ownerAC.animSpeed = 0.1;
1518 1521
1519 1522 // Helper highlight function for the formatter
1520 1523 var highlightMatch = function (full, snippet, matchindex) {
1521 1524 return full.substring(0, matchindex)
1522 1525 + "<span class='match'>"
1523 1526 + full.substr(matchindex, snippet.length)
1524 1527 + "</span>" + full.substring(matchindex + snippet.length);
1525 1528 };
1526 1529
1527 1530 // Custom formatter to highlight the matching letters
1528 1531 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1529 1532 var org_sQuery = sQuery;
1530 1533 if(this.dataSource.mentionQuery != null){
1531 1534 sQuery = this.dataSource.mentionQuery;
1532 1535 }
1533 1536
1534 1537 var query = sQuery.toLowerCase();
1535 1538 var _gravatar = function(res, em, group){
1536 1539 if (group !== undefined){
1537 1540 em = '/images/icons/group.png'
1538 1541 }
1539 1542 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1540 1543 return tmpl.format(em,res)
1541 1544 }
1542 1545 if (oResultData.nname != undefined) {
1543 1546 var fname = oResultData.fname || "";
1544 1547 var lname = oResultData.lname || "";
1545 1548 var nname = oResultData.nname;
1546 1549
1547 1550 // Guard against null value
1548 1551 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1549 1552 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1550 1553 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1551 1554 displayfname, displaylname, displaynname;
1552 1555
1553 1556 if (fnameMatchIndex > -1) {
1554 1557 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1555 1558 } else {
1556 1559 displayfname = fname;
1557 1560 }
1558 1561
1559 1562 if (lnameMatchIndex > -1) {
1560 1563 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1561 1564 } else {
1562 1565 displaylname = lname;
1563 1566 }
1564 1567
1565 1568 if (nnameMatchIndex > -1) {
1566 1569 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1567 1570 } else {
1568 1571 displaynname = nname ? "(" + nname + ")" : "";
1569 1572 }
1570 1573
1571 1574 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1572 1575 } else {
1573 1576 return '';
1574 1577 }
1575 1578 };
1576 1579
1577 1580 if(ownerAC.itemSelectEvent){
1578 1581 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1579 1582
1580 1583 var myAC = aArgs[0]; // reference back to the AC instance
1581 1584 var elLI = aArgs[1]; // reference to the selected LI element
1582 1585 var oData = aArgs[2]; // object literal of selected item's result data
1583 1586 //fill the autocomplete with value
1584 1587 if (oData.nname != undefined) {
1585 1588 //users
1586 1589 //Replace the mention name with replaced
1587 1590 var re = new RegExp();
1588 1591 var org = myAC.getInputEl().value;
1589 1592 var chunks = myAC.dataSource.chunks
1590 1593 // replace middle chunk(the search term) with actuall match
1591 1594 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1592 1595 '@'+oData.nname+' ');
1593 1596 myAC.getInputEl().value = chunks.join('')
1594 1597 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1595 1598 } else {
1596 1599 //groups
1597 1600 myAC.getInputEl().value = oData.grname;
1598 1601 YUD.get('perm_new_member_type').value = 'users_group';
1599 1602 }
1600 1603 });
1601 1604 }
1602 1605
1603 1606 // in this keybuffer we will gather current value of search !
1604 1607 // since we need to get this just when someone does `@` then we do the
1605 1608 // search
1606 1609 ownerAC.dataSource.chunks = [];
1607 1610 ownerAC.dataSource.mentionQuery = null;
1608 1611
1609 1612 ownerAC.get_mention = function(msg, max_pos) {
1610 1613 var org = msg;
1611 1614 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1612 1615 var chunks = [];
1613 1616
1614 1617
1615 1618 // cut first chunk until curret pos
1616 1619 var to_max = msg.substr(0, max_pos);
1617 1620 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1618 1621 var msg2 = to_max.substr(at_pos);
1619 1622
1620 1623 chunks.push(org.substr(0,at_pos))// prefix chunk
1621 1624 chunks.push(msg2) // search chunk
1622 1625 chunks.push(org.substr(max_pos)) // postfix chunk
1623 1626
1624 1627 // clean up msg2 for filtering and regex match
1625 1628 var msg2 = msg2.lstrip(' ').lstrip('\n');
1626 1629
1627 1630 if(re.test(msg2)){
1628 1631 var unam = re.exec(msg2)[1];
1629 1632 return [unam, chunks];
1630 1633 }
1631 1634 return [null, null];
1632 1635 };
1633 1636
1634 1637 if (ownerAC.textboxKeyUpEvent){
1635 1638 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1636 1639
1637 1640 var ac_obj = args[0];
1638 1641 var currentMessage = args[1];
1639 1642 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1640 1643
1641 1644 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1642 1645 var curr_search = null;
1643 1646 if(unam[0]){
1644 1647 curr_search = unam[0];
1645 1648 }
1646 1649
1647 1650 ownerAC.dataSource.chunks = unam[1];
1648 1651 ownerAC.dataSource.mentionQuery = curr_search;
1649 1652
1650 1653 })
1651 1654 }
1652 1655 return {
1653 1656 ownerDS: ownerDS,
1654 1657 ownerAC: ownerAC,
1655 1658 };
1656 1659 }
1657 1660
1658 1661 var addReviewMember = function(id,fname,lname,nname,gravatar_link){
1659 1662 var members = YUD.get('review_members');
1660 1663 var tmpl = '<li id="reviewer_{2}">'+
1661 1664 '<div class="reviewers_member">'+
1662 1665 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1663 1666 '<div style="float:left">{1}</div>'+
1664 1667 '<input type="hidden" value="{2}" name="review_members" />'+
1665 1668 '<span class="delete_icon action_button" onclick="removeReviewMember({2})"></span>'+
1666 1669 '</div>'+
1667 1670 '</li>' ;
1668 1671 var displayname = "{0} {1} ({2})".format(fname,lname,nname);
1669 1672 var element = tmpl.format(gravatar_link,displayname,id);
1670 1673 // check if we don't have this ID already in
1671 1674 var ids = [];
1672 1675 var _els = YUQ('#review_members li');
1673 1676 for (el in _els){
1674 1677 ids.push(_els[el].id)
1675 1678 }
1676 1679 if(ids.indexOf('reviewer_'+id) == -1){
1677 1680 //only add if it's not there
1678 1681 members.innerHTML += element;
1679 1682 }
1680 1683
1681 1684 }
1682 1685
1683 1686 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1684 1687 var el = YUD.get('reviewer_{0}'.format(reviewer_id));
1685 1688 if (el.parentNode !== undefined){
1686 1689 el.parentNode.removeChild(el);
1687 1690 }
1688 1691 }
1689 1692
1690 1693 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
1691 1694 if (reviewers_ids === undefined){
1692 1695 var reviewers_ids = [];
1693 1696 var ids = YUQ('#review_members input');
1694 1697 for(var i=0; i<ids.length;i++){
1695 1698 var id = ids[i].value
1696 1699 reviewers_ids.push(id);
1697 1700 }
1698 1701 }
1699 1702 var url = pyroutes.url('pullrequest_update', {"repo_name":repo_name,
1700 1703 "pull_request_id": pull_request_id});
1701 1704 var postData = {'_method':'put',
1702 1705 'reviewers_ids': reviewers_ids};
1703 1706 var success = function(o){
1704 1707 window.location.reload();
1705 1708 }
1706 1709 ajaxPOST(url,postData,success);
1707 1710 }
1708 1711
1709 1712 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1710 1713 var myUsers = users_list;
1711 1714 var myGroups = groups_list;
1712 1715
1713 1716 // Define a custom search function for the DataSource of users
1714 1717 var matchUsers = function (sQuery) {
1715 1718 // Case insensitive matching
1716 1719 var query = sQuery.toLowerCase();
1717 1720 var i = 0;
1718 1721 var l = myUsers.length;
1719 1722 var matches = [];
1720 1723
1721 1724 // Match against each name of each contact
1722 1725 for (; i < l; i++) {
1723 1726 contact = myUsers[i];
1724 1727 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1725 1728 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1726 1729 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1727 1730 matches[matches.length] = contact;
1728 1731 }
1729 1732 }
1730 1733 return matches;
1731 1734 };
1732 1735
1733 1736 // Define a custom search function for the DataSource of userGroups
1734 1737 var matchGroups = function (sQuery) {
1735 1738 // Case insensitive matching
1736 1739 var query = sQuery.toLowerCase();
1737 1740 var i = 0;
1738 1741 var l = myGroups.length;
1739 1742 var matches = [];
1740 1743
1741 1744 // Match against each name of each contact
1742 1745 for (; i < l; i++) {
1743 1746 matched_group = myGroups[i];
1744 1747 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1745 1748 matches[matches.length] = matched_group;
1746 1749 }
1747 1750 }
1748 1751 return matches;
1749 1752 };
1750 1753
1751 1754 //match all
1752 1755 var matchAll = function (sQuery) {
1753 1756 u = matchUsers(sQuery);
1754 1757 return u
1755 1758 };
1756 1759
1757 1760 // DataScheme for owner
1758 1761 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1759 1762
1760 1763 ownerDS.responseSchema = {
1761 1764 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1762 1765 };
1763 1766
1764 1767 // Instantiate AutoComplete for mentions
1765 1768 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1766 1769 reviewerAC.useShadow = false;
1767 1770 reviewerAC.resultTypeList = false;
1768 1771 reviewerAC.suppressInputUpdate = true;
1769 1772 reviewerAC.animVert = false;
1770 1773 reviewerAC.animHoriz = false;
1771 1774 reviewerAC.animSpeed = 0.1;
1772 1775
1773 1776 // Helper highlight function for the formatter
1774 1777 var highlightMatch = function (full, snippet, matchindex) {
1775 1778 return full.substring(0, matchindex)
1776 1779 + "<span class='match'>"
1777 1780 + full.substr(matchindex, snippet.length)
1778 1781 + "</span>" + full.substring(matchindex + snippet.length);
1779 1782 };
1780 1783
1781 1784 // Custom formatter to highlight the matching letters
1782 1785 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1783 1786 var org_sQuery = sQuery;
1784 1787 if(this.dataSource.mentionQuery != null){
1785 1788 sQuery = this.dataSource.mentionQuery;
1786 1789 }
1787 1790
1788 1791 var query = sQuery.toLowerCase();
1789 1792 var _gravatar = function(res, em, group){
1790 1793 if (group !== undefined){
1791 1794 em = '/images/icons/group.png'
1792 1795 }
1793 1796 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1794 1797 return tmpl.format(em,res)
1795 1798 }
1796 1799 if (oResultData.nname != undefined) {
1797 1800 var fname = oResultData.fname || "";
1798 1801 var lname = oResultData.lname || "";
1799 1802 var nname = oResultData.nname;
1800 1803
1801 1804 // Guard against null value
1802 1805 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1803 1806 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1804 1807 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1805 1808 displayfname, displaylname, displaynname;
1806 1809
1807 1810 if (fnameMatchIndex > -1) {
1808 1811 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1809 1812 } else {
1810 1813 displayfname = fname;
1811 1814 }
1812 1815
1813 1816 if (lnameMatchIndex > -1) {
1814 1817 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1815 1818 } else {
1816 1819 displaylname = lname;
1817 1820 }
1818 1821
1819 1822 if (nnameMatchIndex > -1) {
1820 1823 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1821 1824 } else {
1822 1825 displaynname = nname ? "(" + nname + ")" : "";
1823 1826 }
1824 1827
1825 1828 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1826 1829 } else {
1827 1830 return '';
1828 1831 }
1829 1832 };
1830 1833
1831 1834 //members cache to catch duplicates
1832 1835 reviewerAC.dataSource.cache = [];
1833 1836 // hack into select event
1834 1837 if(reviewerAC.itemSelectEvent){
1835 1838 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1836 1839
1837 1840 var myAC = aArgs[0]; // reference back to the AC instance
1838 1841 var elLI = aArgs[1]; // reference to the selected LI element
1839 1842 var oData = aArgs[2]; // object literal of selected item's result data
1840 1843
1841 1844 //fill the autocomplete with value
1842 1845
1843 1846 if (oData.nname != undefined) {
1844 1847 addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
1845 1848 oData.gravatar_lnk);
1846 1849 myAC.dataSource.cache.push(oData.id);
1847 1850 YUD.get('user').value = ''
1848 1851 }
1849 1852 });
1850 1853 }
1851 1854 return {
1852 1855 ownerDS: ownerDS,
1853 1856 reviewerAC: reviewerAC,
1854 1857 };
1855 1858 }
1856 1859
1857 1860 /**
1858 1861 * QUICK REPO MENU
1859 1862 */
1860 1863 var quick_repo_menu = function(){
1861 1864 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1862 1865 var menu = e.currentTarget.firstElementChild.firstElementChild;
1863 1866 if(YUD.hasClass(menu,'hidden')){
1864 1867 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1865 1868 YUD.replaceClass(menu, 'hidden', 'active');
1866 1869 }
1867 1870 })
1868 1871 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1869 1872 var menu = e.currentTarget.firstElementChild.firstElementChild;
1870 1873 if(YUD.hasClass(menu,'active')){
1871 1874 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1872 1875 YUD.replaceClass(menu, 'active', 'hidden');
1873 1876 }
1874 1877 })
1875 1878 };
1876 1879
1877 1880
1878 1881 /**
1879 1882 * TABLE SORTING
1880 1883 */
1881 1884
1882 1885 // returns a node from given html;
1883 1886 var fromHTML = function(html){
1884 1887 var _html = document.createElement('element');
1885 1888 _html.innerHTML = html;
1886 1889 return _html;
1887 1890 }
1888 1891 var get_rev = function(node){
1889 1892 var n = node.firstElementChild.firstElementChild;
1890 1893
1891 1894 if (n===null){
1892 1895 return -1
1893 1896 }
1894 1897 else{
1895 1898 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1896 1899 return parseInt(out);
1897 1900 }
1898 1901 }
1899 1902
1900 1903 var get_name = function(node){
1901 1904 var name = node.firstElementChild.children[2].innerHTML;
1902 1905 return name
1903 1906 }
1904 1907 var get_group_name = function(node){
1905 1908 var name = node.firstElementChild.children[1].innerHTML;
1906 1909 return name
1907 1910 }
1908 1911 var get_date = function(node){
1909 1912 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1910 1913 return date_
1911 1914 }
1912 1915
1913 1916 var get_age = function(node){
1914 1917 return node
1915 1918 }
1916 1919
1917 1920 var get_link = function(node){
1918 1921 return node.firstElementChild.text;
1919 1922 }
1920 1923
1921 1924 var revisionSort = function(a, b, desc, field) {
1922 1925
1923 1926 var a_ = fromHTML(a.getData(field));
1924 1927 var b_ = fromHTML(b.getData(field));
1925 1928
1926 1929 // extract revisions from string nodes
1927 1930 a_ = get_rev(a_)
1928 1931 b_ = get_rev(b_)
1929 1932
1930 1933 var comp = YAHOO.util.Sort.compare;
1931 1934 var compState = comp(a_, b_, desc);
1932 1935 return compState;
1933 1936 };
1934 1937 var ageSort = function(a, b, desc, field) {
1935 1938 var a_ = fromHTML(a.getData(field));
1936 1939 var b_ = fromHTML(b.getData(field));
1937 1940
1938 1941 // extract name from table
1939 1942 a_ = get_date(a_)
1940 1943 b_ = get_date(b_)
1941 1944
1942 1945 var comp = YAHOO.util.Sort.compare;
1943 1946 var compState = comp(a_, b_, desc);
1944 1947 return compState;
1945 1948 };
1946 1949
1947 1950 var lastLoginSort = function(a, b, desc, field) {
1948 1951 var a_ = a.getData('last_login_raw') || 0;
1949 1952 var b_ = b.getData('last_login_raw') || 0;
1950 1953
1951 1954 var comp = YAHOO.util.Sort.compare;
1952 1955 var compState = comp(a_, b_, desc);
1953 1956 return compState;
1954 1957 };
1955 1958
1956 1959 var nameSort = function(a, b, desc, field) {
1957 1960 var a_ = fromHTML(a.getData(field));
1958 1961 var b_ = fromHTML(b.getData(field));
1959 1962
1960 1963 // extract name from table
1961 1964 a_ = get_name(a_)
1962 1965 b_ = get_name(b_)
1963 1966
1964 1967 var comp = YAHOO.util.Sort.compare;
1965 1968 var compState = comp(a_, b_, desc);
1966 1969 return compState;
1967 1970 };
1968 1971
1969 1972 var permNameSort = function(a, b, desc, field) {
1970 1973 var a_ = fromHTML(a.getData(field));
1971 1974 var b_ = fromHTML(b.getData(field));
1972 1975 // extract name from table
1973 1976
1974 1977 a_ = a_.children[0].innerHTML;
1975 1978 b_ = b_.children[0].innerHTML;
1976 1979
1977 1980 var comp = YAHOO.util.Sort.compare;
1978 1981 var compState = comp(a_, b_, desc);
1979 1982 return compState;
1980 1983 };
1981 1984
1982 1985 var groupNameSort = function(a, b, desc, field) {
1983 1986 var a_ = fromHTML(a.getData(field));
1984 1987 var b_ = fromHTML(b.getData(field));
1985 1988
1986 1989 // extract name from table
1987 1990 a_ = get_group_name(a_)
1988 1991 b_ = get_group_name(b_)
1989 1992
1990 1993 var comp = YAHOO.util.Sort.compare;
1991 1994 var compState = comp(a_, b_, desc);
1992 1995 return compState;
1993 1996 };
1994 1997 var dateSort = function(a, b, desc, field) {
1995 1998 var a_ = fromHTML(a.getData(field));
1996 1999 var b_ = fromHTML(b.getData(field));
1997 2000
1998 2001 // extract name from table
1999 2002 a_ = get_date(a_)
2000 2003 b_ = get_date(b_)
2001 2004
2002 2005 var comp = YAHOO.util.Sort.compare;
2003 2006 var compState = comp(a_, b_, desc);
2004 2007 return compState;
2005 2008 };
2006 2009
2007 2010 var usernamelinkSort = function(a, b, desc, field) {
2008 2011 var a_ = fromHTML(a.getData(field));
2009 2012 var b_ = fromHTML(b.getData(field));
2010 2013
2011 2014 // extract url text from string nodes
2012 2015 a_ = get_link(a_)
2013 2016 b_ = get_link(b_)
2014 2017 var comp = YAHOO.util.Sort.compare;
2015 2018 var compState = comp(a_, b_, desc);
2016 2019 return compState;
2017 2020 }
2018 2021
2019 2022 var addPermAction = function(_html, users_list, groups_list){
2020 2023 var elmts = YUD.getElementsByClassName('last_new_member');
2021 2024 var last_node = elmts[elmts.length-1];
2022 2025 if (last_node){
2023 2026 var next_id = (YUD.getElementsByClassName('new_members')).length;
2024 2027 _html = _html.format(next_id);
2025 2028 last_node.innerHTML = _html;
2026 2029 YUD.setStyle(last_node, 'display', '');
2027 2030 YUD.removeClass(last_node, 'last_new_member');
2028 2031 MembersAutoComplete("perm_new_member_name_"+next_id,
2029 2032 "perm_container_"+next_id, users_list, groups_list);
2030 2033 //create new last NODE
2031 2034 var el = document.createElement('tr');
2032 2035 el.id = 'add_perm_input';
2033 2036 YUD.addClass(el,'last_new_member');
2034 2037 YUD.addClass(el,'new_members');
2035 2038 YUD.insertAfter(el, last_node);
2036 2039 }
2037 2040 }
2041 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
2042 var callback = {
2043 success: function (o) {
2044 var tr = YUD.get(String(field_id));
2045 tr.parentNode.removeChild(tr);
2046 },
2047 failure: function (o) {
2048 alert(_TM['Failed to remoke permission'] + ": " + o.status);
2049 },
2050 };
2051 query_params = {
2052 '_method': 'delete'
2053 }
2054 // put extra data into POST
2055 if (extra_data !== undefined && (typeof extra_data === 'object')){
2056 for(k in extra_data){
2057 query_params[k] = extra_data[k];
2058 }
2059 }
2038 2060
2061 if (obj_type=='user'){
2062 query_params['user_id'] = obj_id;
2063 query_params['obj_type'] = 'user';
2064 }
2065 else if (obj_type=='user_group'){
2066 query_params['user_group_id'] = obj_id;
2067 query_params['obj_type'] = 'user_group';
2068 }
2069
2070 var request = YAHOO.util.Connect.asyncRequest('POST', url, callback,
2071 toQueryString(query_params));
2072 };
2039 2073 /* Multi selectors */
2040 2074
2041 2075 var MultiSelectWidget = function(selected_id, available_id, form_id){
2042 2076
2043 2077
2044 2078 //definition of containers ID's
2045 2079 var selected_container = selected_id;
2046 2080 var available_container = available_id;
2047 2081
2048 2082 //temp container for selected storage.
2049 2083 var cache = new Array();
2050 2084 var av_cache = new Array();
2051 2085 var c = YUD.get(selected_container);
2052 2086 var ac = YUD.get(available_container);
2053 2087
2054 2088 //get only selected options for further fullfilment
2055 2089 for(var i = 0;node =c.options[i];i++){
2056 2090 if(node.selected){
2057 2091 //push selected to my temp storage left overs :)
2058 2092 cache.push(node);
2059 2093 }
2060 2094 }
2061 2095
2062 2096 //get all available options to cache
2063 2097 for(var i = 0;node =ac.options[i];i++){
2064 2098 //push selected to my temp storage left overs :)
2065 2099 av_cache.push(node);
2066 2100 }
2067 2101
2068 2102 //fill available only with those not in chosen
2069 2103 ac.options.length=0;
2070 2104 tmp_cache = new Array();
2071 2105
2072 2106 for(var i = 0;node = av_cache[i];i++){
2073 2107 var add = true;
2074 2108 for(var i2 = 0;node_2 = cache[i2];i2++){
2075 2109 if(node.value == node_2.value){
2076 2110 add=false;
2077 2111 break;
2078 2112 }
2079 2113 }
2080 2114 if(add){
2081 2115 tmp_cache.push(new Option(node.text, node.value, false, false));
2082 2116 }
2083 2117 }
2084 2118
2085 2119 for(var i = 0;node = tmp_cache[i];i++){
2086 2120 ac.options[i] = node;
2087 2121 }
2088 2122
2089 2123 function prompts_action_callback(e){
2090 2124
2091 2125 var chosen = YUD.get(selected_container);
2092 2126 var available = YUD.get(available_container);
2093 2127
2094 2128 //get checked and unchecked options from field
2095 2129 function get_checked(from_field){
2096 2130 //temp container for storage.
2097 2131 var sel_cache = new Array();
2098 2132 var oth_cache = new Array();
2099 2133
2100 2134 for(var i = 0;node = from_field.options[i];i++){
2101 2135 if(node.selected){
2102 2136 //push selected fields :)
2103 2137 sel_cache.push(node);
2104 2138 }
2105 2139 else{
2106 2140 oth_cache.push(node)
2107 2141 }
2108 2142 }
2109 2143
2110 2144 return [sel_cache,oth_cache]
2111 2145 }
2112 2146
2113 2147 //fill the field with given options
2114 2148 function fill_with(field,options){
2115 2149 //clear firtst
2116 2150 field.options.length=0;
2117 2151 for(var i = 0;node = options[i];i++){
2118 2152 field.options[i]=new Option(node.text, node.value,
2119 2153 false, false);
2120 2154 }
2121 2155
2122 2156 }
2123 2157 //adds to current field
2124 2158 function add_to(field,options){
2125 2159 for(var i = 0;node = options[i];i++){
2126 2160 field.appendChild(new Option(node.text, node.value,
2127 2161 false, false));
2128 2162 }
2129 2163 }
2130 2164
2131 2165 // add action
2132 2166 if (this.id=='add_element'){
2133 2167 var c = get_checked(available);
2134 2168 add_to(chosen,c[0]);
2135 2169 fill_with(available,c[1]);
2136 2170 }
2137 2171 // remove action
2138 2172 if (this.id=='remove_element'){
2139 2173 var c = get_checked(chosen);
2140 2174 add_to(available,c[0]);
2141 2175 fill_with(chosen,c[1]);
2142 2176 }
2143 2177 // add all elements
2144 2178 if(this.id=='add_all_elements'){
2145 2179 for(var i=0; node = available.options[i];i++){
2146 2180 chosen.appendChild(new Option(node.text,
2147 2181 node.value, false, false));
2148 2182 }
2149 2183 available.options.length = 0;
2150 2184 }
2151 2185 //remove all elements
2152 2186 if(this.id=='remove_all_elements'){
2153 2187 for(var i=0; node = chosen.options[i];i++){
2154 2188 available.appendChild(new Option(node.text,
2155 2189 node.value, false, false));
2156 2190 }
2157 2191 chosen.options.length = 0;
2158 2192 }
2159 2193
2160 2194 }
2161 2195
2162 2196 YUE.addListener(['add_element','remove_element',
2163 2197 'add_all_elements','remove_all_elements'],'click',
2164 2198 prompts_action_callback)
2165 2199 if (form_id !== undefined) {
2166 2200 YUE.addListener(form_id,'submit',function(){
2167 2201 var chosen = YUD.get(selected_container);
2168 2202 for (var i = 0; i < chosen.options.length; i++) {
2169 2203 chosen.options[i].selected = 'selected';
2170 2204 }
2171 2205 });
2172 2206 }
2173 2207 }
2174 2208
2175 2209
2176 2210 // global hooks after DOM is loaded
2177 2211
2178 2212 YUE.onDOMReady(function(){
2179 2213 YUE.on(YUQ('.diff-collapse-button'), 'click', function(e){
2180 2214 var button = e.currentTarget;
2181 2215 var t = YUD.get(button).getAttribute('target');
2182 2216 console.log(t);
2183 2217 if(YUD.hasClass(t, 'hidden')){
2184 2218 YUD.removeClass(t, 'hidden');
2185 2219 YUD.get(button).innerHTML = "&uarr; {0} &uarr;".format(_TM['Collapse diff']);
2186 2220 }
2187 2221 else if(!YUD.hasClass(t, 'hidden')){
2188 2222 YUD.addClass(t, 'hidden');
2189 2223 YUD.get(button).innerHTML = "&darr; {0} &darr;".format(_TM['Expand diff']);
2190 2224 }
2191 2225 });
2192 2226
2193 2227
2194 2228
2195 2229 });
@@ -1,122 +1,103 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repo_info.repo_to_perm:
12 12 %if r2p.user.username =='default' and c.repo_info.private:
13 13 <tr>
14 14 <td colspan="4">
15 15 <span class="private_repo_msg">
16 16 ${_('private repository')}
17 17 </span>
18 18 </td>
19 19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${_('default')}</td>
20 20 </tr>
21 21 %else:
22 22 <tr id="id${id(r2p.user.username)}">
23 23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 27 <td style="white-space: nowrap;">
28 28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
29 29 </td>
30 30 <td>
31 31 %if r2p.user.username !='default':
32 32 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}')">
33 33 ${_('revoke')}
34 34 </span>
35 35 %endif
36 36 </td>
37 37 </tr>
38 38 %endif
39 39 %endfor
40 40
41 41 ## USER GROUPS
42 42 %for g2p in c.repo_info.users_group_to_perm:
43 43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 48 <td style="white-space: nowrap;">
49 49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>
50 50 %if h.HasPermissionAny('hg.admin')():
51 51 <a href="${h.url('edit_users_group',id=g2p.users_group.users_group_id)}">${g2p.users_group.users_group_name}</a>
52 52 %else:
53 53 ${g2p.users_group.users_group_name}
54 54 %endif
55 55 </td>
56 56 <td>
57 57 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}')">
58 58 ${_('revoke')}
59 59 </span>
60 60 </td>
61 61 </tr>
62 62 %endfor
63 63 <%
64 64 _tmpl = h.literal("""' \
65 65 <td><input type="radio" value="repository.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 66 <td><input type="radio" value="repository.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
67 67 <td><input type="radio" value="repository.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
68 68 <td><input type="radio" value="repository.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
69 69 <td class="ac"> \
70 70 <div class="perm_ac" id="perm_ac_{0}"> \
71 71 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
72 72 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
73 73 <div id="perm_container_{0}"></div> \
74 74 </div> \
75 75 </td> \
76 76 <td></td>'""")
77 77 %>
78 78 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
79 79 <tr class="new_members last_new_member" id="add_perm_input"></tr>
80 80 <tr>
81 81 <td colspan="6">
82 82 <span id="add_perm" class="add_icon" style="cursor: pointer;">
83 83 ${_('Add another member')}
84 84 </span>
85 85 </td>
86 86 </tr>
87 87 </table>
88 88 <script type="text/javascript">
89 89 function ajaxActionRevoke(obj_id, obj_type, field_id) {
90 var callback = {
91 success: function (o) {
92 var tr = YUD.get(String(field_id));
93 tr.parentNode.removeChild(tr);
94 },
95 failure: function (o) {
96 alert(_TM['Failed to remoke permission'] + ": " + o.status);
97 },
90 url = "${h.url('delete_repo_perm_member',repo_name=c.repo_name)}";
91 ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
98 92 };
99 if (obj_type=='user'){
100 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
101 var postData = '_method=delete&user_id={0}&obj_type=user'.format(obj_id);
102 }
103 else if (obj_type=='user_group'){
104 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
105 var postData = '_method=delete&users_group_id={0}&obj_type=user_group'.format(obj_id);
106
107 }
108
109 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
110 };
111
112 93
113 94 YUE.onDOMReady(function () {
114 95 if (!YUD.hasClass('perm_new_member_name', 'error')) {
115 96 YUD.setStyle('add_perm_input', 'display', 'none');
116 97 }
117 98 YAHOO.util.Event.addListener('add_perm', 'click', function () {
118 99 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
119 100 });
120 101 });
121 102
122 103 </script>
@@ -1,125 +1,106 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repos_group.repo_group_to_perm:
12 12 ##forbid revoking permission from yourself
13 13 <tr id="id${id(r2p.user.username)}">
14 14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
17 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
18 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
19 19 <td style="white-space: nowrap;">
20 20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 21 </td>
22 22 <td>
23 23 %if r2p.user.username !='default':
24 24 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}')">
25 25 ${_('revoke')}
26 26 </span>
27 27 %endif
28 28 </td>
29 29 %else:
30 30 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none', disabled="disabled")}</td>
31 31 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read', disabled="disabled")}</td>
32 32 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write', disabled="disabled")}</td>
33 33 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin', disabled="disabled")}</td>
34 34 <td style="white-space: nowrap;">
35 35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 36 </td>
37 37 <td>
38 38 </td>
39 39 %endif
40 40 </tr>
41 41 %endfor
42 42
43 43 ## USER GROUPS
44 44 %for g2p in c.repos_group.users_group_to_perm:
45 45 <tr id="id${id(g2p.users_group.users_group_name)}">
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
48 48 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
49 49 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
50 50 <td style="white-space: nowrap;">
51 51 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
52 52 </td>
53 53 <td>
54 54 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${g2p.users_group.users_group_id}, 'user_group', '${'id%s'%id(g2p.users_group.users_group_name)}')">
55 55 ${_('revoke')}
56 56 </span>
57 57 </td>
58 58 </tr>
59 59 %endfor
60 60 <%
61 61 _tmpl = h.literal("""' \
62 62 <td><input type="radio" value="group.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
63 63 <td><input type="radio" value="group.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
64 64 <td><input type="radio" value="group.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
65 65 <td><input type="radio" value="group.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
66 66 <td class="ac"> \
67 67 <div class="perm_ac" id="perm_ac_{0}"> \
68 68 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
69 69 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
70 70 <div id="perm_container_{0}"></div> \
71 71 </div> \
72 72 </td> \
73 73 <td></td>'""")
74 74 %>
75 75 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
76 76 <tr class="new_members last_new_member" id="add_perm_input"></tr>
77 77 <tr>
78 78 <td colspan="6">
79 79 <span id="add_perm" class="add_icon" style="cursor: pointer;">
80 80 ${_('Add another member')}
81 81 </span>
82 82 </td>
83 83 </tr>
84 84 <tr>
85 85 <td colspan="6">
86 86 ${h.checkbox('recursive',value="True", label=_('apply to children'))}
87 87 <span class="help-block">${_('Set or revoke permission to all children of that group, including non-private repositories and other groups')}</span>
88 88 </td>
89 89 </tr>
90 90 </table>
91 91 <script type="text/javascript">
92 92 function ajaxActionRevoke(obj_id, obj_type, field_id) {
93 var callback = {
94 success: function (o) {
95 var tr = YUD.get(String(field_id));
96 tr.parentNode.removeChild(tr);
97 },
98 failure: function (o) {
99 alert(_TM['Failed to remoke permission'] + ": " + o.status);
100 },
93 url = "${h.url('delete_repo_group_perm_member', group_name=c.repos_group.group_name)}";
94 ajaxActionRevokePermission(url, obj_id, obj_type, field_id, {recursive:YUD.get('recursive').checked});
101 95 };
102 var recursive = YUD.get('recursive').checked;
103
104 if (obj_type=='user'){
105 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
106 var postData = '_method=delete&recursive={0}&user_id={1}&obj_type=user'.format(recursive,obj_id);
107 }
108 else if (obj_type=='user_group'){
109 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
110 var postData = '_method=delete&recursive={0}&users_group_id={0}&obj_type=user_group'.format(recursive,obj_id);
111 }
112 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
113 };
114
115 96
116 97 YUE.onDOMReady(function () {
117 98 if (!YUD.hasClass('perm_new_member_name', 'error')) {
118 99 YUD.setStyle('add_perm_input', 'display', 'none');
119 100 }
120 101 YAHOO.util.Event.addListener('add_perm', 'click', function () {
121 102 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
122 103 });
123 104 });
124 105
125 106 </script>
@@ -1,100 +1,83 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.users_group.user_user_group_to_perm:
12 12 ##forbid revoking permission from yourself
13 13 <tr id="id${id(r2p.user.username)}">
14 14 %if c.rhodecode_user.user_id != r2p.user.user_id or c.rhodecode_user.is_admin:
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read')}</td>
17 17 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write')}</td>
18 18 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin')}</td>
19 19 <td style="white-space: nowrap;">
20 20 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
21 21 </td>
22 22 <td>
23 23 %if r2p.user.username !='default':
24 24 <span class="delete_icon action_button" onclick="ajaxActionRevoke(${r2p.user.user_id}, 'user', '${'id%s'%id(r2p.user.username)}')">
25 25 ${_('revoke')}
26 26 </span>
27 27 %endif
28 28 </td>
29 29 %else:
30 30 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.none', disabled="disabled")}</td>
31 31 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.read', disabled="disabled")}</td>
32 32 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.write', disabled="disabled")}</td>
33 33 <td>${h.radio('u_perm_%s' % r2p.user.username,'usergroup.admin', disabled="disabled")}</td>
34 34 <td style="white-space: nowrap;">
35 35 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username if r2p.user.username != 'default' else _('default')}
36 36 </td>
37 37 <td>
38 38 </td>
39 39 %endif
40 40 </tr>
41 41 %endfor
42 42
43 43 <%
44 44 _tmpl = h.literal("""' \
45 45 <td><input type="radio" value="usergroup.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
46 46 <td><input type="radio" value="usergroup.read" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
47 47 <td><input type="radio" value="usergroup.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
48 48 <td><input type="radio" value="usergroup.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td> \
49 49 <td class="ac"> \
50 50 <div class="perm_ac" id="perm_ac_{0}"> \
51 51 <input class="yui-ac-input" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text"> \
52 52 <input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden"> \
53 53 <div id="perm_container_{0}"></div> \
54 54 </div> \
55 55 </td> \
56 56 <td></td>'""")
57 57 %>
58 58 ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl'
59 59 <tr class="new_members last_new_member" id="add_perm_input"></tr>
60 60 <tr>
61 61 <td colspan="6">
62 62 <span id="add_perm" class="add_icon" style="cursor: pointer;">
63 63 ${_('Add another member')}
64 64 </span>
65 65 </td>
66 66 </tr>
67 67 </table>
68 68 <script type="text/javascript">
69 69 function ajaxActionRevoke(obj_id, obj_type, field_id) {
70 var callback = {
71 success: function (o) {
72 var tr = YUD.get(String(field_id));
73 tr.parentNode.removeChild(tr);
74 },
75 failure: function (o) {
76 alert(_TM['Failed to remoke permission'] + ": " + o.status);
77 },
70 url = "${h.url('delete_user_group_perm_member', id=c.users_group.users_group_id)}";
71 ajaxActionRevokePermission(url, obj_id, obj_type, field_id);
78 72 };
79 var sUrl = "${h.url('delete_user_group_perm_member', id=c.users_group.users_group_id)}";
80 if (obj_type=='user'){
81 var postData = '_method=delete&user_id={0}&obj_type=user'.format(obj_id);
82 }
83 else if (obj_type=='user_group'){
84 var postData = '_method=delete&user_group_id={0}&obj_type=user_group'.format(obj_id);
85 }
86
87 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
88 };
89
90 73
91 74 YUE.onDOMReady(function () {
92 75 if (!YUD.hasClass('perm_new_member_name', 'error')) {
93 76 YUD.setStyle('add_perm_input', 'display', 'none');
94 77 }
95 78 YAHOO.util.Event.addListener('add_perm', 'click', function () {
96 79 addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n});
97 80 });
98 81 });
99 82
100 83 </script>
General Comments 0
You need to be logged in to leave comments. Login now