##// END OF EJS Templates
spelling: chosen
Mads Kiilerich -
r3622:91ff741c beta
parent child Browse files
Show More
@@ -1,735 +1,735 b''
1 1 .. _setup:
2 2
3 3 =====
4 4 Setup
5 5 =====
6 6
7 7
8 8 Setting up RhodeCode
9 9 --------------------
10 10
11 11 First, you will need to create a RhodeCode configuration file. Run the
12 12 following command to do this::
13 13
14 14 paster make-config RhodeCode production.ini
15 15
16 16 - This will create the file `production.ini` in the current directory. This
17 17 configuration file contains the various settings for RhodeCode, e.g proxy
18 18 port, email settings, usage of static files, cache, celery settings and
19 19 logging.
20 20
21 21
22 22 Next, you need to create the databases used by RhodeCode. I recommend that you
23 23 use postgresql or sqlite (default). If you choose a database other than the
24 24 default ensure you properly adjust the db url in your production.ini
25 25 configuration file to use this other database. RhodeCode currently supports
26 26 postgresql, sqlite and mysql databases. Create the database by running
27 27 the following command::
28 28
29 29 paster setup-rhodecode production.ini
30 30
31 31 This will prompt you for a "root" path. This "root" path is the location where
32 32 RhodeCode will store all of its repositories on the current machine. After
33 33 entering this "root" path ``setup-rhodecode`` will also prompt you for a username
34 34 and password for the initial admin account which ``setup-rhodecode`` sets
35 35 up for you.
36 36
37 37 setup process can be fully automated, example for lazy::
38 38
39 39 paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos
40 40
41 41
42 42 - The ``setup-rhodecode`` command will create all of the needed tables and an
43 43 admin account. When choosing a root path you can either use a new empty
44 44 location, or a location which already contains existing repositories. If you
45 45 choose a location which contains existing repositories RhodeCode will simply
46 46 add all of the repositories at the chosen location to it's database.
47 47 (Note: make sure you specify the correct path to the root).
48 48 - Note: the given path for mercurial_ repositories **must** be write accessible
49 49 for the application. It's very important since the RhodeCode web interface
50 50 will work without write access, but when trying to do a push it will
51 51 eventually fail with permission denied errors unless it has write access.
52 52
53 53 You are now ready to use RhodeCode, to run it simply execute::
54 54
55 55 paster serve production.ini
56 56
57 57 - This command runs the RhodeCode server. The web app should be available at the
58 58 127.0.0.1:5000. This ip and port is configurable via the production.ini
59 59 file created in previous step
60 60 - Use the admin account you created above when running ``setup-rhodecode``
61 61 to login to the web app.
62 62 - The default permissions on each repository is read, and the owner is admin.
63 63 Remember to update these if needed.
64 64 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
65 65 well as edit more advanced options on users and repositories
66 66
67 67 Optionally users can create `rcextensions` package that extends RhodeCode
68 68 functionality. To do this simply execute::
69 69
70 70 paster make-rcext production.ini
71 71
72 72 This will create `rcextensions` package in the same place that your `ini` file
73 73 lives. With `rcextensions` it's possible to add additional mapping for whoosh,
74 74 stats and add additional code into the push/pull/create/delete repo hooks.
75 75 For example for sending signals to build-bots such as jenkins.
76 76 Please see the `__init__.py` file inside `rcextensions` package
77 77 for more details.
78 78
79 79
80 80 Using RhodeCode with SSH
81 81 ------------------------
82 82
83 83 RhodeCode currently only hosts repositories using http and https. (The addition
84 84 of ssh hosting is a planned future feature.) However you can easily use ssh in
85 85 parallel with RhodeCode. (Repository access via ssh is a standard "out of
86 86 the box" feature of mercurial_ and you can use this to access any of the
87 87 repositories that RhodeCode is hosting. See PublishingRepositories_)
88 88
89 89 RhodeCode repository structures are kept in directories with the same name
90 90 as the project. When using repository groups, each group is a subdirectory.
91 91 This allows you to easily use ssh for accessing repositories.
92 92
93 93 In order to use ssh you need to make sure that your web-server and the users
94 94 login accounts have the correct permissions set on the appropriate directories.
95 95 (Note that these permissions are independent of any permissions you have set up
96 96 using the RhodeCode web interface.)
97 97
98 98 If your main directory (the same as set in RhodeCode settings) is for example
99 99 set to **/home/hg** and the repository you are using is named `rhodecode`, then
100 100 to clone via ssh you should run::
101 101
102 102 hg clone ssh://user@server.com/home/hg/rhodecode
103 103
104 104 Using other external tools such as mercurial-server_ or using ssh key based
105 105 authentication is fully supported.
106 106
107 107 Note: In an advanced setup, in order for your ssh access to use the same
108 108 permissions as set up via the RhodeCode web interface, you can create an
109 109 authentication hook to connect to the rhodecode db and runs check functions for
110 110 permissions against that.
111 111
112 112 Setting up Whoosh full text search
113 113 ----------------------------------
114 114
115 115 Starting from version 1.1 the whoosh index can be build by using the paster
116 116 command ``make-index``. To use ``make-index`` you must specify the configuration
117 117 file that stores the location of the index. You may specify the location of the
118 118 repositories (`--repo-location`). If not specified, this value is retrieved
119 119 from the RhodeCode database. This was required prior to 1.2. Starting from
120 120 version 1.2 it is also possible to specify a comma separated list of
121 121 repositories (`--index-only`) to build index only on chooses repositories
122 122 skipping any other found in repos location
123 123
124 124 You may optionally pass the option `-f` to enable a full index rebuild. Without
125 125 the `-f` option, indexing will run always in "incremental" mode.
126 126
127 127 For an incremental index build use::
128 128
129 129 paster make-index production.ini
130 130
131 131 For a full index rebuild use::
132 132
133 133 paster make-index production.ini -f
134 134
135 135
136 136 building index just for chosen repositories is possible with such command::
137 137
138 138 paster make-index production.ini --index-only=vcs,rhodecode
139 139
140 140
141 141 In order to do periodical index builds and keep your index always up to date.
142 142 It's recommended to do a crontab entry for incremental indexing.
143 143 An example entry might look like this::
144 144
145 145 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
146 146
147 147 When using incremental mode (the default) whoosh will check the last
148 148 modification date of each file and add it to be reindexed if a newer file is
149 149 available. The indexing daemon checks for any removed files and removes them
150 150 from index.
151 151
152 152 If you want to rebuild index from scratch, you can use the `-f` flag as above,
153 153 or in the admin panel you can check `build from scratch` flag.
154 154
155 155
156 156 Setting up LDAP support
157 157 -----------------------
158 158
159 159 RhodeCode starting from version 1.1 supports ldap authentication. In order
160 160 to use LDAP, you have to install the python-ldap_ package. This package is
161 161 available via pypi, so you can install it by running
162 162
163 163 using easy_install::
164 164
165 165 easy_install python-ldap
166 166
167 167 using pip::
168 168
169 169 pip install python-ldap
170 170
171 171 .. note::
172 172 python-ldap requires some certain libs on your system, so before installing
173 173 it check that you have at least `openldap`, and `sasl` libraries.
174 174
175 175 LDAP settings are located in admin->ldap section,
176 176
177 177 Here's a typical ldap setup::
178 178
179 179 Connection settings
180 180 Enable LDAP = checked
181 181 Host = host.example.org
182 182 Port = 389
183 183 Account = <account>
184 184 Password = <password>
185 185 Connection Security = LDAPS connection
186 186 Certificate Checks = DEMAND
187 187
188 188 Search settings
189 189 Base DN = CN=users,DC=host,DC=example,DC=org
190 190 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
191 191 LDAP Search Scope = SUBTREE
192 192
193 193 Attribute mappings
194 194 Login Attribute = uid
195 195 First Name Attribute = firstName
196 196 Last Name Attribute = lastName
197 197 E-mail Attribute = mail
198 198
199 199 .. _enable_ldap:
200 200
201 201 Enable LDAP : required
202 202 Whether to use LDAP for authenticating users.
203 203
204 204 .. _ldap_host:
205 205
206 206 Host : required
207 207 LDAP server hostname or IP address. Can be also a comma separated
208 208 list of servers to support LDAP fail-over.
209 209
210 210 .. _Port:
211 211
212 212 Port : required
213 213 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
214 214
215 215 .. _ldap_account:
216 216
217 217 Account : optional
218 218 Only required if the LDAP server does not allow anonymous browsing of
219 219 records. This should be a special account for record browsing. This
220 220 will require `LDAP Password`_ below.
221 221
222 222 .. _LDAP Password:
223 223
224 224 Password : optional
225 225 Only required if the LDAP server does not allow anonymous browsing of
226 226 records.
227 227
228 228 .. _Enable LDAPS:
229 229
230 230 Connection Security : required
231 231 Defines the connection to LDAP server
232 232
233 233 No encryption
234 234 Plain non encrypted connection
235 235
236 236 LDAPS connection
237 237 Enable ldaps connection. It will likely require `Port`_ to be set to
238 238 a different value (standard LDAPS port is 636). When LDAPS is enabled
239 239 then `Certificate Checks`_ is required.
240 240
241 241 START_TLS on LDAP connection
242 242 START TLS connection
243 243
244 244 .. _Certificate Checks:
245 245
246 246 Certificate Checks : optional
247 247 How SSL certificates verification is handled - this is only useful when
248 248 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
249 249 while the other options are susceptible to man-in-the-middle attacks. SSL
250 250 certificates can be installed to /etc/openldap/cacerts so that the
251 251 DEMAND or HARD options can be used with self-signed certificates or
252 252 certificates that do not have traceable certificates of authority.
253 253
254 254 NEVER
255 255 A serve certificate will never be requested or checked.
256 256
257 257 ALLOW
258 258 A server certificate is requested. Failure to provide a
259 259 certificate or providing a bad certificate will not terminate the
260 260 session.
261 261
262 262 TRY
263 263 A server certificate is requested. Failure to provide a
264 264 certificate does not halt the session; providing a bad certificate
265 265 halts the session.
266 266
267 267 DEMAND
268 268 A server certificate is requested and must be provided and
269 269 authenticated for the session to proceed.
270 270
271 271 HARD
272 272 The same as DEMAND.
273 273
274 274 .. _Base DN:
275 275
276 276 Base DN : required
277 277 The Distinguished Name (DN) where searches for users will be performed.
278 278 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
279 279
280 280 .. _LDAP Filter:
281 281
282 282 LDAP Filter : optional
283 283 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
284 284 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
285 285 which LDAP objects are identified as representing Users for
286 286 authentication. The filter is augmented by `Login Attribute`_ below.
287 287 This can commonly be left blank.
288 288
289 289 .. _LDAP Search Scope:
290 290
291 291 LDAP Search Scope : required
292 292 This limits how far LDAP will search for a matching object.
293 293
294 294 BASE
295 295 Only allows searching of `Base DN`_ and is usually not what you
296 296 want.
297 297
298 298 ONELEVEL
299 299 Searches all entries under `Base DN`_, but not Base DN itself.
300 300
301 301 SUBTREE
302 302 Searches all entries below `Base DN`_, but not Base DN itself.
303 303 When using SUBTREE `LDAP Filter`_ is useful to limit object
304 304 location.
305 305
306 306 .. _Login Attribute:
307 307
308 308 Login Attribute : required
309 309 The LDAP record attribute that will be matched as the USERNAME or
310 310 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
311 311 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
312 312 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
313 313 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
314 314 ::
315 315
316 316 (&(LDAPFILTER)(uid=jsmith))
317 317
318 318 .. _ldap_attr_firstname:
319 319
320 320 First Name Attribute : required
321 321 The LDAP record attribute which represents the user's first name.
322 322
323 323 .. _ldap_attr_lastname:
324 324
325 325 Last Name Attribute : required
326 326 The LDAP record attribute which represents the user's last name.
327 327
328 328 .. _ldap_attr_email:
329 329
330 330 Email Attribute : required
331 331 The LDAP record attribute which represents the user's email address.
332 332
333 333 If all data are entered correctly, and python-ldap_ is properly installed
334 334 users should be granted access to RhodeCode with ldap accounts. At this
335 335 time user information is copied from LDAP into the RhodeCode user database.
336 336 This means that updates of an LDAP user object may not be reflected as a
337 337 user update in RhodeCode.
338 338
339 339 If You have problems with LDAP access and believe You entered correct
340 340 information check out the RhodeCode logs, any error messages sent from LDAP
341 341 will be saved there.
342 342
343 343 Active Directory
344 344 ''''''''''''''''
345 345
346 346 RhodeCode can use Microsoft Active Directory for user authentication. This
347 347 is done through an LDAP or LDAPS connection to Active Directory. The
348 348 following LDAP configuration settings are typical for using Active
349 349 Directory ::
350 350
351 351 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
352 352 Login Attribute = sAMAccountName
353 353 First Name Attribute = givenName
354 354 Last Name Attribute = sn
355 355 E-mail Attribute = mail
356 356
357 357 All other LDAP settings will likely be site-specific and should be
358 358 appropriately configured.
359 359
360 360
361 361 Authentication by container or reverse-proxy
362 362 --------------------------------------------
363 363
364 364 Starting with version 1.3, RhodeCode supports delegating the authentication
365 365 of users to its WSGI container, or to a reverse-proxy server through which all
366 366 clients access the application.
367 367
368 368 When these authentication methods are enabled in RhodeCode, it uses the
369 369 username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
370 370 perform the authentication itself. The authorization, however, is still done by
371 371 RhodeCode according to its settings.
372 372
373 373 When a user logs in for the first time using these authentication methods,
374 374 a matching user account is created in RhodeCode with default permissions. An
375 375 administrator can then modify it using RhodeCode's admin interface.
376 376 It's also possible for an administrator to create accounts and configure their
377 377 permissions before the user logs in for the first time.
378 378
379 379 Container-based authentication
380 380 ''''''''''''''''''''''''''''''
381 381
382 382 In a container-based authentication setup, RhodeCode reads the user name from
383 383 the ``REMOTE_USER`` server variable provided by the WSGI container.
384 384
385 385 After setting up your container (see `Apache's WSGI config`_), you'd need
386 386 to configure it to require authentication on the location configured for
387 387 RhodeCode.
388 388
389 389 In order for RhodeCode to start using the provided username, you should set the
390 390 following in the [app:main] section of your .ini file::
391 391
392 392 container_auth_enabled = true
393 393
394 394
395 395 Proxy pass-through authentication
396 396 '''''''''''''''''''''''''''''''''
397 397
398 398 In a proxy pass-through authentication setup, RhodeCode reads the user name
399 399 from the ``X-Forwarded-User`` request header, which should be configured to be
400 400 sent by the reverse-proxy server.
401 401
402 402 After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
403 403 `Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
404 404 configure the authentication and add the username in a request header named
405 405 ``X-Forwarded-User``.
406 406
407 407 For example, the following config section for Apache sets a subdirectory in a
408 408 reverse-proxy setup with basic auth::
409 409
410 410 <Location /<someprefix> >
411 411 ProxyPass http://127.0.0.1:5000/<someprefix>
412 412 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
413 413 SetEnvIf X-Url-Scheme https HTTPS=1
414 414
415 415 AuthType Basic
416 416 AuthName "RhodeCode authentication"
417 417 AuthUserFile /home/web/rhodecode/.htpasswd
418 418 require valid-user
419 419
420 420 RequestHeader unset X-Forwarded-User
421 421
422 422 RewriteEngine On
423 423 RewriteCond %{LA-U:REMOTE_USER} (.+)
424 424 RewriteRule .* - [E=RU:%1]
425 425 RequestHeader set X-Forwarded-User %{RU}e
426 426 </Location>
427 427
428 428 In order for RhodeCode to start using the forwarded username, you should set
429 429 the following in the [app:main] section of your .ini file::
430 430
431 431 proxypass_auth_enabled = true
432 432
433 433 .. note::
434 434 If you enable proxy pass-through authentication, make sure your server is
435 435 only accessible through the proxy. Otherwise, any client would be able to
436 436 forge the authentication header and could effectively become authenticated
437 437 using any account of their liking.
438 438
439 439 Integration with Issue trackers
440 440 -------------------------------
441 441
442 442 RhodeCode provides a simple integration with issue trackers. It's possible
443 443 to define a regular expression that will fetch issue id stored in commit
444 444 messages and replace that with an url to this issue. To enable this simply
445 445 uncomment following variables in the ini file::
446 446
447 447 url_pat = (?:^#|\s#)(\w+)
448 448 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
449 449 issue_prefix = #
450 450
451 451 `url_pat` is the regular expression that will fetch issues from commit messages.
452 452 Default regex will match issues in format of #<number> eg. #300.
453 453
454 454 Matched issues will be replace with the link specified as `issue_server_link`
455 455 {id} will be replaced with issue id, and {repo} with repository name.
456 456 Since the # is striped `issue_prefix` is added as a prefix to url.
457 457 `issue_prefix` can be something different than # if you pass
458 458 ISSUE- as issue prefix this will generate an url in format::
459 459
460 460 <a href="https://myissueserver.com/example_repo/issue/300">ISSUE-300</a>
461 461
462 462 Hook management
463 463 ---------------
464 464
465 465 Hooks can be managed in similar way to this used in .hgrc files.
466 466 To access hooks setting click `advanced setup` on Hooks section of Mercurial
467 467 Settings in Admin.
468 468
469 469 There are 4 built in hooks that cannot be changed (only enable/disable by
470 470 checkboxes on previos section).
471 471 To add another custom hook simply fill in first section with
472 472 <name>.<hook_type> and the second one with hook path. Example hooks
473 473 can be found at *rhodecode.lib.hooks*.
474 474
475 475
476 476 Changing default encoding
477 477 -------------------------
478 478
479 479 By default RhodeCode uses utf8 encoding, starting from 1.3 series this
480 480 can be changed, simply edit default_encoding in .ini file to desired one.
481 481 This affects many parts in rhodecode including committers names, filenames,
482 482 encoding of commit messages. In addition RhodeCode can detect if `chardet`
483 483 library is installed. If `chardet` is detected RhodeCode will fallback to it
484 484 when there are encode/decode errors.
485 485
486 486
487 487 Setting Up Celery
488 488 -----------------
489 489
490 490 Since version 1.1 celery is configured by the rhodecode ini configuration files.
491 491 Simply set use_celery=true in the ini file then add / change the configuration
492 492 variables inside the ini file.
493 493
494 494 Remember that the ini files use the format with '.' not with '_' like celery.
495 495 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
496 496 the config file.
497 497
498 498 In order to start using celery run::
499 499
500 500 paster celeryd <configfile.ini>
501 501
502 502
503 503 .. note::
504 504 Make sure you run this command from the same virtualenv, and with the same
505 505 user that rhodecode runs.
506 506
507 507 HTTPS support
508 508 -------------
509 509
510 510 There are two ways to enable https:
511 511
512 512 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
513 513 recognize this headers and make proper https redirections
514 514 - Alternatively, change the `force_https = true` flag in the ini configuration
515 515 to force using https, no headers are needed than to enable https
516 516
517 517
518 518 Nginx virtual host example
519 519 --------------------------
520 520
521 521 Sample config for nginx using proxy::
522 522
523 523 upstream rc {
524 524 server 127.0.0.1:5000;
525 525 # add more instances for load balancing
526 526 #server 127.0.0.1:5001;
527 527 #server 127.0.0.1:5002;
528 528 }
529 529
530 530 server {
531 531 listen 443;
532 532 server_name rhodecode.myserver.com;
533 533 access_log /var/log/nginx/rhodecode.access.log;
534 534 error_log /var/log/nginx/rhodecode.error.log;
535 535
536 536 ssl on;
537 537 ssl_certificate rhodecode.myserver.com.crt;
538 538 ssl_certificate_key rhodecode.myserver.com.key;
539 539
540 540 ssl_session_timeout 5m;
541 541
542 542 ssl_protocols SSLv3 TLSv1;
543 543 ssl_ciphers DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:EDH-RSA-DES-CBC3-SHA:AES256-SHA:DES-CBC3-SHA:AES128-SHA:RC4-SHA:RC4-MD5;
544 544 ssl_prefer_server_ciphers on;
545 545
546 546 # uncomment if you have nginx with chunking module compiled
547 547 # fixes the issues of having to put postBuffer data for large git
548 548 # pushes
549 549 #chunkin on;
550 550 #error_page 411 = @my_411_error;
551 551 #location @my_411_error {
552 552 # chunkin_resume;
553 553 #}
554 554
555 555 # uncomment if you want to serve static files by nginx
556 556 #root /path/to/installation/rhodecode/public;
557 557
558 558 location / {
559 559 try_files $uri @rhode;
560 560 }
561 561
562 562 location @rhode {
563 563 proxy_pass http://rc;
564 564 include /etc/nginx/proxy.conf;
565 565 }
566 566
567 567 }
568 568
569 569 Here's the proxy.conf. It's tuned so it will not timeout on long
570 570 pushes or large pushes::
571 571
572 572 proxy_redirect off;
573 573 proxy_set_header Host $host;
574 574 proxy_set_header X-Url-Scheme $scheme;
575 575 proxy_set_header X-Host $http_host;
576 576 proxy_set_header X-Real-IP $remote_addr;
577 577 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
578 578 proxy_set_header Proxy-host $proxy_host;
579 579 client_max_body_size 400m;
580 580 client_body_buffer_size 128k;
581 581 proxy_buffering off;
582 582 proxy_connect_timeout 7200;
583 583 proxy_send_timeout 7200;
584 584 proxy_read_timeout 7200;
585 585 proxy_buffers 8 32k;
586 586
587 587 Also, when using root path with nginx you might set the static files to false
588 588 in the production.ini file::
589 589
590 590 [app:main]
591 591 use = egg:rhodecode
592 592 full_stack = true
593 593 static_files = false
594 594 lang=en
595 595 cache_dir = %(here)s/data
596 596
597 597 In order to not have the statics served by the application. This improves speed.
598 598
599 599
600 600 Apache virtual host reverse proxy example
601 601 -----------------------------------------
602 602
603 603 Here is a sample configuration file for apache using proxy::
604 604
605 605 <VirtualHost *:80>
606 606 ServerName hg.myserver.com
607 607 ServerAlias hg.myserver.com
608 608
609 609 <Proxy *>
610 610 Order allow,deny
611 611 Allow from all
612 612 </Proxy>
613 613
614 614 #important !
615 615 #Directive to properly generate url (clone url) for pylons
616 616 ProxyPreserveHost On
617 617
618 618 #rhodecode instance
619 619 ProxyPass / http://127.0.0.1:5000/
620 620 ProxyPassReverse / http://127.0.0.1:5000/
621 621
622 622 #to enable https use line below
623 623 #SetEnvIf X-Url-Scheme https HTTPS=1
624 624
625 625 </VirtualHost>
626 626
627 627
628 628 Additional tutorial
629 629 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
630 630
631 631
632 632 Apache as subdirectory
633 633 ----------------------
634 634
635 635 Apache subdirectory part::
636 636
637 637 <Location /<someprefix> >
638 638 ProxyPass http://127.0.0.1:5000/<someprefix>
639 639 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
640 640 SetEnvIf X-Url-Scheme https HTTPS=1
641 641 </Location>
642 642
643 643 Besides the regular apache setup you will need to add the following line
644 644 into [app:main] section of your .ini file::
645 645
646 646 filter-with = proxy-prefix
647 647
648 648 Add the following at the end of the .ini file::
649 649
650 650 [filter:proxy-prefix]
651 651 use = egg:PasteDeploy#prefix
652 652 prefix = /<someprefix>
653 653
654 654
655 then change <someprefix> into your choosen prefix
655 then change <someprefix> into your chosen prefix
656 656
657 657 Apache's WSGI config
658 658 --------------------
659 659
660 660 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
661 661 that, you'll need to:
662 662
663 663 - Install mod_wsgi. If using a Debian-based distro, you can install
664 664 the package libapache2-mod-wsgi::
665 665
666 666 aptitude install libapache2-mod-wsgi
667 667
668 668 - Enable mod_wsgi::
669 669
670 670 a2enmod wsgi
671 671
672 672 - Create a wsgi dispatch script, like the one below. Make sure you
673 673 check the paths correctly point to where you installed RhodeCode
674 674 and its Python Virtual Environment.
675 675 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
676 676 as in the following example. Once again, check the paths are
677 677 correctly specified.
678 678
679 679 Here is a sample excerpt from an Apache Virtual Host configuration file::
680 680
681 681 WSGIDaemonProcess pylons \
682 682 threads=4 \
683 683 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
684 684 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
685 685 WSGIPassAuthorization On
686 686
687 687 .. note::
688 688 when running apache as root please add: `user=www-data group=www-data`
689 689 into above configuration
690 690
691 691 .. note::
692 692 Running RhodeCode in multiprocess mode in apache is not supported,
693 693 make sure you don't specify `processes=num` directive in the config
694 694
695 695
696 696 Example wsgi dispatch script::
697 697
698 698 import os
699 699 os.environ["HGENCODING"] = "UTF-8"
700 700 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
701 701
702 702 # sometimes it's needed to set the curent dir
703 703 os.chdir('/home/web/rhodecode/')
704 704
705 705 import site
706 706 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
707 707
708 708 from paste.deploy import loadapp
709 709 from paste.script.util.logging_config import fileConfig
710 710
711 711 fileConfig('/home/web/rhodecode/production.ini')
712 712 application = loadapp('config:/home/web/rhodecode/production.ini')
713 713
714 714 Note: when using mod_wsgi you'll need to install the same version of
715 715 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
716 716 environment.
717 717
718 718
719 719 Other configuration files
720 720 -------------------------
721 721
722 722 Some example init.d scripts can be found in init.d directory::
723 723
724 724 https://secure.rhodecode.org/rhodecode/files/beta/init.d
725 725
726 726 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
727 727 .. _python: http://www.python.org/
728 728 .. _mercurial: http://mercurial.selenic.com/
729 729 .. _celery: http://celeryproject.org/
730 730 .. _rabbitmq: http://www.rabbitmq.com/
731 731 .. _python-ldap: http://www.python-ldap.org/
732 732 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
733 733 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
734 734 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
735 735 .. _google group rhodecode: http://groups.google.com/group/rhodecode
@@ -1,2175 +1,2175 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 371 * Partial Ajax Implementation
372 372 *
373 373 * @param url: defines url to make partial request
374 374 * @param container: defines id of container to input partial result
375 375 * @param s_call: success callback function that takes o as arg
376 376 * o.tId
377 377 * o.status
378 378 * o.statusText
379 379 * o.getResponseHeader[ ]
380 380 * o.getAllResponseHeaders
381 381 * o.responseText
382 382 * o.responseXML
383 383 * o.argument
384 384 * @param f_call: failure callback
385 385 * @param args arguments
386 386 */
387 387 function ypjax(url,container,s_call,f_call,args){
388 388 var method='GET';
389 389 if(args===undefined){
390 390 args=null;
391 391 }
392 392
393 393 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
394 394 YUC.initHeader('X-PARTIAL-XHR',true);
395 395
396 396 // wrapper of passed callback
397 397 var s_wrapper = (function(o){
398 398 return function(o){
399 399 YUD.get(container).innerHTML=o.responseText;
400 400 YUD.setStyle(container,'opacity','1.0');
401 401 //execute the given original callback
402 402 if (s_call !== undefined){
403 403 s_call(o);
404 404 }
405 405 }
406 406 })()
407 407 YUD.setStyle(container,'opacity','0.3');
408 408 YUC.asyncRequest(method,url,{
409 409 success:s_wrapper,
410 410 failure:function(o){
411 411 console.log(o);
412 412 YUD.get(container).innerHTML='<span class="error_red">ERROR: {0}</span>'.format(o.status);
413 413 YUD.setStyle(container,'opacity','1.0');
414 414 },
415 415 cache:false
416 416 },args);
417 417
418 418 };
419 419
420 420 var ajaxGET = function(url,success) {
421 421 // Set special header for ajax == HTTP_X_PARTIAL_XHR
422 422 YUC.initHeader('X-PARTIAL-XHR',true);
423 423
424 424 var sUrl = url;
425 425 var callback = {
426 426 success: success,
427 427 failure: function (o) {
428 428 if (o.status != 0) {
429 429 alert("error: " + o.statusText);
430 430 };
431 431 },
432 432 };
433 433
434 434 var request = YAHOO.util.Connect.asyncRequest('GET', sUrl, callback);
435 435 return request;
436 436 };
437 437
438 438
439 439
440 440 var ajaxPOST = function(url,postData,success) {
441 441 // Set special header for ajax == HTTP_X_PARTIAL_XHR
442 442 YUC.initHeader('X-PARTIAL-XHR',true);
443 443
444 444 var toQueryString = function(o) {
445 445 if(typeof o !== 'object') {
446 446 return false;
447 447 }
448 448 var _p, _qs = [];
449 449 for(_p in o) {
450 450 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
451 451 }
452 452 return _qs.join('&');
453 453 };
454 454
455 455 var sUrl = url;
456 456 var callback = {
457 457 success: success,
458 458 failure: function (o) {
459 459 alert("error");
460 460 },
461 461 };
462 462 var postData = toQueryString(postData);
463 463 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
464 464 return request;
465 465 };
466 466
467 467
468 468 /**
469 469 * tooltip activate
470 470 */
471 471 var tooltip_activate = function(){
472 472 yt = YAHOO.yuitip.main;
473 473 YUE.onDOMReady(yt.init);
474 474 };
475 475
476 476 /**
477 477 * show more
478 478 */
479 479 var show_more_event = function(){
480 480 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
481 481 var el = e.target;
482 482 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
483 483 YUD.setStyle(el.parentNode,'display','none');
484 484 });
485 485 };
486 486
487 487 /**
488 488 * show changeset tooltip
489 489 */
490 490 var show_changeset_tooltip = function(){
491 491 YUE.on(YUD.getElementsByClassName('lazy-cs'), 'mouseover', function(e){
492 492 var target = e.currentTarget;
493 493 var rid = YUD.getAttribute(target,'raw_id');
494 494 var repo_name = YUD.getAttribute(target,'repo_name');
495 495 var ttid = 'tt-'+rid;
496 496 var success = function(o){
497 497 var json = JSON.parse(o.responseText);
498 498 YUD.addClass(target,'tooltip')
499 499 YUD.setAttribute(target, 'title',json['message']);
500 500 YAHOO.yuitip.main.show_yuitip(e, target);
501 501 }
502 502 if(rid && !YUD.hasClass(target, 'tooltip')){
503 503 YUD.setAttribute(target,'id',ttid);
504 504 YUD.setAttribute(target, 'title',_TM['loading...']);
505 505 YAHOO.yuitip.main.set_listeners(target);
506 506 YAHOO.yuitip.main.show_yuitip(e, target);
507 507 var url = pyroutes.url('changeset_info', {"repo_name":repo_name, "revision": rid});
508 508 ajaxGET(url, success)
509 509 }
510 510 });
511 511 };
512 512
513 513 var onSuccessFollow = function(target){
514 514 var f = YUD.get(target);
515 515 var f_cnt = YUD.get('current_followers_count');
516 516
517 517 if(YUD.hasClass(f, 'follow')){
518 518 f.setAttribute('class','following');
519 519 f.setAttribute('title',_TM['Stop following this repository']);
520 520
521 521 if(f_cnt){
522 522 var cnt = Number(f_cnt.innerHTML)+1;
523 523 f_cnt.innerHTML = cnt;
524 524 }
525 525 }
526 526 else{
527 527 f.setAttribute('class','follow');
528 528 f.setAttribute('title',_TM['Start following this repository']);
529 529 if(f_cnt){
530 530 var cnt = Number(f_cnt.innerHTML)-1;
531 531 f_cnt.innerHTML = cnt;
532 532 }
533 533 }
534 534 }
535 535
536 536 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
537 537 args = 'follows_user_id='+fallows_user_id;
538 538 args+= '&amp;auth_token='+token;
539 539 if(user_id != undefined){
540 540 args+="&amp;user_id="+user_id;
541 541 }
542 542 YUC.asyncRequest('POST',TOGGLE_FOLLOW_URL,{
543 543 success:function(o){
544 544 onSuccessFollow(target);
545 545 }
546 546 },args);
547 547 return false;
548 548 }
549 549
550 550 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
551 551
552 552 args = 'follows_repo_id='+fallows_repo_id;
553 553 args+= '&amp;auth_token='+token;
554 554 if(user_id != undefined){
555 555 args+="&amp;user_id="+user_id;
556 556 }
557 557 YUC.asyncRequest('POST',TOGGLE_FOLLOW_URL,{
558 558 success:function(o){
559 559 onSuccessFollow(target);
560 560 }
561 561 },args);
562 562 return false;
563 563 }
564 564
565 565 var showRepoSize = function(target, repo_name, token){
566 566 var args= 'auth_token='+token;
567 567
568 568 if(!YUD.hasClass(target, 'loaded')){
569 569 YUD.get(target).innerHTML = _TM['Loading ...'];
570 570 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
571 571 YUC.asyncRequest('POST',url,{
572 572 success:function(o){
573 573 YUD.get(target).innerHTML = JSON.parse(o.responseText);
574 574 YUD.addClass(target, 'loaded');
575 575 }
576 576 },args);
577 577 }
578 578 return false;
579 579 }
580 580
581 581 /**
582 582 * TOOLTIP IMPL.
583 583 */
584 584 YAHOO.namespace('yuitip');
585 585 YAHOO.yuitip.main = {
586 586
587 587 $: YAHOO.util.Dom.get,
588 588
589 589 bgColor: '#000',
590 590 speed: 0.3,
591 591 opacity: 0.9,
592 592 offset: [15,15],
593 593 useAnim: false,
594 594 maxWidth: 600,
595 595 add_links: false,
596 596 yuitips: [],
597 597
598 598 set_listeners: function(tt){
599 599 YUE.on(tt, 'mouseover', yt.show_yuitip, tt);
600 600 YUE.on(tt, 'mousemove', yt.move_yuitip, tt);
601 601 YUE.on(tt, 'mouseout', yt.close_yuitip, tt);
602 602 },
603 603
604 604 init: function(){
605 605 yt.tipBox = yt.$('tip-box');
606 606 if(!yt.tipBox){
607 607 yt.tipBox = document.createElement('div');
608 608 document.body.appendChild(yt.tipBox);
609 609 yt.tipBox.id = 'tip-box';
610 610 }
611 611
612 612 YUD.setStyle(yt.tipBox, 'display', 'none');
613 613 YUD.setStyle(yt.tipBox, 'position', 'absolute');
614 614 if(yt.maxWidth !== null){
615 615 YUD.setStyle(yt.tipBox, 'max-width', yt.maxWidth+'px');
616 616 }
617 617
618 618 var yuitips = YUD.getElementsByClassName('tooltip');
619 619
620 620 if(yt.add_links === true){
621 621 var links = document.getElementsByTagName('a');
622 622 var linkLen = links.length;
623 623 for(i=0;i<linkLen;i++){
624 624 yuitips.push(links[i]);
625 625 }
626 626 }
627 627
628 628 var yuiLen = yuitips.length;
629 629
630 630 for(i=0;i<yuiLen;i++){
631 631 yt.set_listeners(yuitips[i]);
632 632 }
633 633 },
634 634
635 635 show_yuitip: function(e, el){
636 636 YUE.stopEvent(e);
637 637 if(el.tagName.toLowerCase() === 'img'){
638 638 yt.tipText = el.alt ? el.alt : '';
639 639 } else {
640 640 yt.tipText = el.title ? el.title : '';
641 641 }
642 642
643 643 if(yt.tipText !== ''){
644 644 // save org title
645 645 YUD.setAttribute(el, 'tt_title', yt.tipText);
646 646 // reset title to not show org tooltips
647 647 YUD.setAttribute(el, 'title', '');
648 648
649 649 yt.tipBox.innerHTML = yt.tipText;
650 650 YUD.setStyle(yt.tipBox, 'display', 'block');
651 651 if(yt.useAnim === true){
652 652 YUD.setStyle(yt.tipBox, 'opacity', '0');
653 653 var newAnim = new YAHOO.util.Anim(yt.tipBox,
654 654 {
655 655 opacity: { to: yt.opacity }
656 656 }, yt.speed, YAHOO.util.Easing.easeOut
657 657 );
658 658 newAnim.animate();
659 659 }
660 660 }
661 661 },
662 662
663 663 move_yuitip: function(e, el){
664 664 YUE.stopEvent(e);
665 665 var movePos = YUE.getXY(e);
666 666 YUD.setStyle(yt.tipBox, 'top', (movePos[1] + yt.offset[1]) + 'px');
667 667 YUD.setStyle(yt.tipBox, 'left', (movePos[0] + yt.offset[0]) + 'px');
668 668 },
669 669
670 670 close_yuitip: function(e, el){
671 671 YUE.stopEvent(e);
672 672
673 673 if(yt.useAnim === true){
674 674 var newAnim = new YAHOO.util.Anim(yt.tipBox,
675 675 {
676 676 opacity: { to: 0 }
677 677 }, yt.speed, YAHOO.util.Easing.easeOut
678 678 );
679 679 newAnim.animate();
680 680 } else {
681 681 YUD.setStyle(yt.tipBox, 'display', 'none');
682 682 }
683 683 YUD.setAttribute(el,'title', YUD.getAttribute(el, 'tt_title'));
684 684 }
685 685 }
686 686
687 687 /**
688 688 * Quick filter widget
689 689 *
690 690 * @param target: filter input target
691 691 * @param nodes: list of nodes in html we want to filter.
692 692 * @param display_element function that takes current node from nodes and
693 693 * does hide or show based on the node
694 694 *
695 695 */
696 696 var q_filter = function(target,nodes,display_element){
697 697
698 698 var nodes = nodes;
699 699 var q_filter_field = YUD.get(target);
700 700 var F = YAHOO.namespace(target);
701 701
702 702 YUE.on(q_filter_field,'click',function(){
703 703 q_filter_field.value = '';
704 704 });
705 705
706 706 YUE.on(q_filter_field,'keyup',function(e){
707 707 clearTimeout(F.filterTimeout);
708 708 F.filterTimeout = setTimeout(F.updateFilter,600);
709 709 });
710 710
711 711 F.filterTimeout = null;
712 712
713 713 var show_node = function(node){
714 714 YUD.setStyle(node,'display','')
715 715 }
716 716 var hide_node = function(node){
717 717 YUD.setStyle(node,'display','none');
718 718 }
719 719
720 720 F.updateFilter = function() {
721 721 // Reset timeout
722 722 F.filterTimeout = null;
723 723
724 724 var obsolete = [];
725 725
726 726 var req = q_filter_field.value.toLowerCase();
727 727
728 728 var l = nodes.length;
729 729 var i;
730 730 var showing = 0;
731 731
732 732 for (i=0;i<l;i++ ){
733 733 var n = nodes[i];
734 734 var target_element = display_element(n)
735 735 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
736 736 hide_node(target_element);
737 737 }
738 738 else{
739 739 show_node(target_element);
740 740 showing+=1;
741 741 }
742 742 }
743 743
744 744 // if repo_count is set update the number
745 745 var cnt = YUD.get('repo_count');
746 746 if(cnt){
747 747 YUD.get('repo_count').innerHTML = showing;
748 748 }
749 749
750 750 }
751 751 };
752 752
753 753 var tableTr = function(cls, body){
754 754 var _el = document.createElement('div');
755 755 var cont = new YAHOO.util.Element(body);
756 756 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
757 757 var id = 'comment-tr-{0}'.format(comment_id);
758 758 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
759 759 '<td class="lineno-inline new-inline"></td>'+
760 760 '<td class="lineno-inline old-inline"></td>'+
761 761 '<td>{2}</td>'+
762 762 '</tr></tbody></table>').format(id, cls, body);
763 763 _el.innerHTML = _html;
764 764 return _el.children[0].children[0].children[0];
765 765 };
766 766
767 767 /** comments **/
768 768 var removeInlineForm = function(form) {
769 769 form.parentNode.removeChild(form);
770 770 };
771 771
772 772 var createInlineForm = function(parent_tr, f_path, line) {
773 773 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
774 774 tmpl = tmpl.format(f_path, line);
775 775 var form = tableTr('comment-form-inline',tmpl)
776 776
777 777 // create event for hide button
778 778 form = new YAHOO.util.Element(form);
779 779 var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]);
780 780 form_hide_button.on('click', function(e) {
781 781 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
782 782 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
783 783 YUD.setStyle(newtr.nextElementSibling,'display','');
784 784 }
785 785 removeInlineForm(newtr);
786 786 YUD.removeClass(parent_tr, 'form-open');
787 787 YUD.removeClass(parent_tr, 'hl-comment');
788 788
789 789 });
790 790
791 791 return form
792 792 };
793 793
794 794 /**
795 795 * Inject inline comment for on given TR this tr should be always an .line
796 796 * tr containing the line. Code will detect comment, and always put the comment
797 797 * block at the very bottom
798 798 */
799 799 var injectInlineForm = function(tr){
800 800 if(!YUD.hasClass(tr, 'line')){
801 801 return
802 802 }
803 803 var submit_url = AJAX_COMMENT_URL;
804 804 var _td = YUD.getElementsByClassName('code',null,tr)[0];
805 805 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
806 806 return
807 807 }
808 808 YUD.addClass(tr,'form-open');
809 809 YUD.addClass(tr,'hl-comment');
810 810 var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0];
811 811 var f_path = YUD.getAttribute(node,'path');
812 812 var lineno = getLineNo(tr);
813 813 var form = createInlineForm(tr, f_path, lineno, submit_url);
814 814
815 815 var parent = tr;
816 816 while (1){
817 817 var n = parent.nextElementSibling;
818 818 // next element are comments !
819 819 if(YUD.hasClass(n,'inline-comments')){
820 820 parent = n;
821 821 }
822 822 else{
823 823 break;
824 824 }
825 825 }
826 826 YUD.insertAfter(form,parent);
827 827 var f = YUD.get(form);
828 828 var overlay = YUD.getElementsByClassName('overlay',null,f)[0];
829 829 var _form = YUD.getElementsByClassName('inline-form',null,f)[0];
830 830
831 831 YUE.on(YUD.get(_form), 'submit',function(e){
832 832 YUE.preventDefault(e);
833 833
834 834 //ajax submit
835 835 var text = YUD.get('text_'+lineno).value;
836 836 var postData = {
837 837 'text':text,
838 838 'f_path':f_path,
839 839 'line':lineno
840 840 };
841 841
842 842 if(lineno === undefined){
843 843 alert('missing line !');
844 844 return
845 845 }
846 846 if(f_path === undefined){
847 847 alert('missing file path !');
848 848 return
849 849 }
850 850
851 851 if(text == ""){
852 852 return
853 853 }
854 854
855 855 var success = function(o){
856 856 YUD.removeClass(tr, 'form-open');
857 857 removeInlineForm(f);
858 858 var json_data = JSON.parse(o.responseText);
859 859 renderInlineComment(json_data);
860 860 };
861 861
862 862 if (YUD.hasClass(overlay,'overlay')){
863 863 var w = _form.offsetWidth;
864 864 var h = _form.offsetHeight;
865 865 YUD.setStyle(overlay,'width',w+'px');
866 866 YUD.setStyle(overlay,'height',h+'px');
867 867 }
868 868 YUD.addClass(overlay, 'submitting');
869 869
870 870 ajaxPOST(submit_url, postData, success);
871 871 });
872 872
873 873 setTimeout(function(){
874 874 // callbacks
875 875 tooltip_activate();
876 876 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
877 877 _USERS_AC_DATA, _GROUPS_AC_DATA);
878 878 var _e = YUD.get('text_'+lineno);
879 879 if(_e){
880 880 _e.focus();
881 881 }
882 882 },10)
883 883 };
884 884
885 885 var deleteComment = function(comment_id){
886 886 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
887 887 var postData = {'_method':'delete'};
888 888 var success = function(o){
889 889 var n = YUD.get('comment-tr-'+comment_id);
890 890 var root = prevElementSibling(prevElementSibling(n));
891 891 n.parentNode.removeChild(n);
892 892
893 893 // scann nodes, and attach add button to last one
894 894 placeAddButton(root);
895 895 }
896 896 ajaxPOST(url,postData,success);
897 897 }
898 898
899 899 var createInlineAddButton = function(tr){
900 900
901 901 var label = TRANSLATION_MAP['Add another comment'];
902 902
903 903 var html_el = document.createElement('div');
904 904 YUD.addClass(html_el, 'add-comment');
905 905 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
906 906
907 907 var add = new YAHOO.util.Element(html_el);
908 908 add.on('click', function(e) {
909 909 injectInlineForm(tr);
910 910 });
911 911 return add;
912 912 };
913 913
914 914 var getLineNo = function(tr) {
915 915 var line;
916 916 var o = tr.children[0].id.split('_');
917 917 var n = tr.children[1].id.split('_');
918 918
919 919 if (n.length >= 2) {
920 920 line = n[n.length-1];
921 921 } else if (o.length >= 2) {
922 922 line = o[o.length-1];
923 923 }
924 924
925 925 return line
926 926 };
927 927
928 928 var placeAddButton = function(target_tr){
929 929 if(!target_tr){
930 930 return
931 931 }
932 932 var last_node = target_tr;
933 933 //scann
934 934 while (1){
935 935 var n = last_node.nextElementSibling;
936 936 // next element are comments !
937 937 if(YUD.hasClass(n,'inline-comments')){
938 938 last_node = n;
939 939 //also remove the comment button from previous
940 940 var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node);
941 941 for(var i=0;i<comment_add_buttons.length;i++){
942 942 var b = comment_add_buttons[i];
943 943 b.parentNode.removeChild(b);
944 944 }
945 945 }
946 946 else{
947 947 break;
948 948 }
949 949 }
950 950
951 951 var add = createInlineAddButton(target_tr);
952 952 // get the comment div
953 953 var comment_block = YUD.getElementsByClassName('comment',null,last_node)[0];
954 954 // attach add button
955 955 YUD.insertAfter(add,comment_block);
956 956 }
957 957
958 958 /**
959 959 * Places the inline comment into the changeset block in proper line position
960 960 */
961 961 var placeInline = function(target_container,lineno,html){
962 962 var lineid = "{0}_{1}".format(target_container,lineno);
963 963 var target_line = YUD.get(lineid);
964 964 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
965 965
966 966 // check if there are comments already !
967 967 var parent = target_line.parentNode;
968 968 var root_parent = parent;
969 969 while (1){
970 970 var n = parent.nextElementSibling;
971 971 // next element are comments !
972 972 if(YUD.hasClass(n,'inline-comments')){
973 973 parent = n;
974 974 }
975 975 else{
976 976 break;
977 977 }
978 978 }
979 979 // put in the comment at the bottom
980 980 YUD.insertAfter(comment,parent);
981 981
982 982 // scann nodes, and attach add button to last one
983 983 placeAddButton(root_parent);
984 984
985 985 return target_line;
986 986 }
987 987
988 988 /**
989 989 * make a single inline comment and place it inside
990 990 */
991 991 var renderInlineComment = function(json_data){
992 992 try{
993 993 var html = json_data['rendered_text'];
994 994 var lineno = json_data['line_no'];
995 995 var target_id = json_data['target_id'];
996 996 placeInline(target_id, lineno, html);
997 997
998 998 }catch(e){
999 999 console.log(e);
1000 1000 }
1001 1001 }
1002 1002
1003 1003 /**
1004 1004 * Iterates over all the inlines, and places them inside proper blocks of data
1005 1005 */
1006 1006 var renderInlineComments = function(file_comments){
1007 1007 for (f in file_comments){
1008 1008 // holding all comments for a FILE
1009 1009 var box = file_comments[f];
1010 1010
1011 1011 var target_id = YUD.getAttribute(box,'target_id');
1012 1012 // actually comments with line numbers
1013 1013 var comments = box.children;
1014 1014 for(var i=0; i<comments.length; i++){
1015 1015 var data = {
1016 1016 'rendered_text': comments[i].outerHTML,
1017 1017 'line_no': YUD.getAttribute(comments[i],'line'),
1018 1018 'target_id': target_id
1019 1019 }
1020 1020 renderInlineComment(data);
1021 1021 }
1022 1022 }
1023 1023 }
1024 1024
1025 1025 var fileBrowserListeners = function(current_url, node_list_url, url_base){
1026 1026 var current_url_branch = +"?branch=__BRANCH__";
1027 1027
1028 1028 YUE.on('stay_at_branch','click',function(e){
1029 1029 if(e.target.checked){
1030 1030 var uri = current_url_branch;
1031 1031 uri = uri.replace('__BRANCH__',e.target.value);
1032 1032 window.location = uri;
1033 1033 }
1034 1034 else{
1035 1035 window.location = current_url;
1036 1036 }
1037 1037 })
1038 1038
1039 1039 var n_filter = YUD.get('node_filter');
1040 1040 var F = YAHOO.namespace('node_filter');
1041 1041
1042 1042 F.filterTimeout = null;
1043 1043 var nodes = null;
1044 1044
1045 1045 F.initFilter = function(){
1046 1046 YUD.setStyle('node_filter_box_loading','display','');
1047 1047 YUD.setStyle('search_activate_id','display','none');
1048 1048 YUD.setStyle('add_node_id','display','none');
1049 1049 YUC.initHeader('X-PARTIAL-XHR',true);
1050 1050 YUC.asyncRequest('GET', node_list_url, {
1051 1051 success:function(o){
1052 1052 nodes = JSON.parse(o.responseText).nodes;
1053 1053 YUD.setStyle('node_filter_box_loading','display','none');
1054 1054 YUD.setStyle('node_filter_box','display','');
1055 1055 n_filter.focus();
1056 1056 if(YUD.hasClass(n_filter,'init')){
1057 1057 n_filter.value = '';
1058 1058 YUD.removeClass(n_filter,'init');
1059 1059 }
1060 1060 },
1061 1061 failure:function(o){
1062 1062 console.log('failed to load');
1063 1063 }
1064 1064 },null);
1065 1065 }
1066 1066
1067 1067 F.updateFilter = function(e) {
1068 1068
1069 1069 return function(){
1070 1070 // Reset timeout
1071 1071 F.filterTimeout = null;
1072 1072 var query = e.target.value.toLowerCase();
1073 1073 var match = [];
1074 1074 var matches = 0;
1075 1075 var matches_max = 20;
1076 1076 if (query != ""){
1077 1077 for(var i=0;i<nodes.length;i++){
1078 1078
1079 1079 var pos = nodes[i].name.toLowerCase().indexOf(query)
1080 1080 if(query && pos != -1){
1081 1081
1082 1082 matches++
1083 1083 //show only certain amount to not kill browser
1084 1084 if (matches > matches_max){
1085 1085 break;
1086 1086 }
1087 1087
1088 1088 var n = nodes[i].name;
1089 1089 var t = nodes[i].type;
1090 1090 var n_hl = n.substring(0,pos)
1091 1091 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
1092 1092 +n.substring(pos+query.length)
1093 1093 var new_url = url_base.replace('__FPATH__',n);
1094 1094 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
1095 1095 }
1096 1096 if(match.length >= matches_max){
1097 1097 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
1098 1098 }
1099 1099 }
1100 1100 }
1101 1101 if(query != ""){
1102 1102 YUD.setStyle('tbody','display','none');
1103 1103 YUD.setStyle('tbody_filtered','display','');
1104 1104
1105 1105 if (match.length==0){
1106 1106 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
1107 1107 }
1108 1108
1109 1109 YUD.get('tbody_filtered').innerHTML = match.join("");
1110 1110 }
1111 1111 else{
1112 1112 YUD.setStyle('tbody','display','');
1113 1113 YUD.setStyle('tbody_filtered','display','none');
1114 1114 }
1115 1115
1116 1116 }
1117 1117 };
1118 1118
1119 1119 YUE.on(YUD.get('filter_activate'),'click',function(){
1120 1120 F.initFilter();
1121 1121 })
1122 1122 YUE.on(n_filter,'click',function(){
1123 1123 if(YUD.hasClass(n_filter,'init')){
1124 1124 n_filter.value = '';
1125 1125 YUD.removeClass(n_filter,'init');
1126 1126 }
1127 1127 });
1128 1128 YUE.on(n_filter,'keyup',function(e){
1129 1129 clearTimeout(F.filterTimeout);
1130 1130 F.filterTimeout = setTimeout(F.updateFilter(e),600);
1131 1131 });
1132 1132 };
1133 1133
1134 1134
1135 1135 var initCodeMirror = function(textAreadId,resetUrl){
1136 1136 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
1137 1137 mode: "null",
1138 1138 lineNumbers:true
1139 1139 });
1140 1140 YUE.on('reset','click',function(e){
1141 1141 window.location=resetUrl
1142 1142 });
1143 1143
1144 1144 YUE.on('file_enable','click',function(){
1145 1145 YUD.setStyle('editor_container','display','');
1146 1146 YUD.setStyle('upload_file_container','display','none');
1147 1147 YUD.setStyle('filename_container','display','');
1148 1148 });
1149 1149
1150 1150 YUE.on('upload_file_enable','click',function(){
1151 1151 YUD.setStyle('editor_container','display','none');
1152 1152 YUD.setStyle('upload_file_container','display','');
1153 1153 YUD.setStyle('filename_container','display','none');
1154 1154 });
1155 1155 };
1156 1156
1157 1157
1158 1158
1159 1159 var getIdentNode = function(n){
1160 1160 //iterate thru nodes untill matched interesting node !
1161 1161
1162 1162 if (typeof n == 'undefined'){
1163 1163 return -1
1164 1164 }
1165 1165
1166 1166 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
1167 1167 return n
1168 1168 }
1169 1169 else{
1170 1170 return getIdentNode(n.parentNode);
1171 1171 }
1172 1172 };
1173 1173
1174 1174 var getSelectionLink = function(e) {
1175 1175
1176 1176 //get selection from start/to nodes
1177 1177 if (typeof window.getSelection != "undefined") {
1178 1178 s = window.getSelection();
1179 1179
1180 1180 from = getIdentNode(s.anchorNode);
1181 1181 till = getIdentNode(s.focusNode);
1182 1182
1183 1183 f_int = parseInt(from.id.replace('L',''));
1184 1184 t_int = parseInt(till.id.replace('L',''));
1185 1185
1186 1186 if (f_int > t_int){
1187 1187 //highlight from bottom
1188 1188 offset = -35;
1189 1189 ranges = [t_int,f_int];
1190 1190
1191 1191 }
1192 1192 else{
1193 1193 //highligth from top
1194 1194 offset = 35;
1195 1195 ranges = [f_int,t_int];
1196 1196 }
1197 1197 // if we select more than 2 lines
1198 1198 if (ranges[0] != ranges[1]){
1199 1199 if(YUD.get('linktt') == null){
1200 1200 hl_div = document.createElement('div');
1201 1201 hl_div.id = 'linktt';
1202 1202 }
1203 1203 hl_div.innerHTML = '';
1204 1204
1205 1205 anchor = '#L'+ranges[0]+'-'+ranges[1];
1206 1206 var link = document.createElement('a');
1207 1207 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
1208 1208 link.innerHTML = _TM['Selection link'];
1209 1209 hl_div.appendChild(link);
1210 1210 YUD.get('body').appendChild(hl_div);
1211 1211
1212 1212 xy = YUD.getXY(till.id);
1213 1213
1214 1214 YUD.addClass('linktt', 'hl-tip-box');
1215 1215 YUD.setStyle('linktt','top',xy[1]+offset+'px');
1216 1216 YUD.setStyle('linktt','left',xy[0]+'px');
1217 1217 YUD.setStyle('linktt','visibility','visible');
1218 1218
1219 1219 }
1220 1220 else{
1221 1221 YUD.setStyle('linktt','visibility','hidden');
1222 1222 }
1223 1223 }
1224 1224 };
1225 1225
1226 1226 var deleteNotification = function(url, notification_id,callbacks){
1227 1227 var callback = {
1228 1228 success:function(o){
1229 1229 var obj = YUD.get(String("notification_"+notification_id));
1230 1230 if(obj.parentNode !== undefined){
1231 1231 obj.parentNode.removeChild(obj);
1232 1232 }
1233 1233 _run_callbacks(callbacks);
1234 1234 },
1235 1235 failure:function(o){
1236 1236 alert("error");
1237 1237 },
1238 1238 };
1239 1239 var postData = '_method=delete';
1240 1240 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1241 1241 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
1242 1242 callback, postData);
1243 1243 };
1244 1244
1245 1245 var readNotification = function(url, notification_id,callbacks){
1246 1246 var callback = {
1247 1247 success:function(o){
1248 1248 var obj = YUD.get(String("notification_"+notification_id));
1249 1249 YUD.removeClass(obj, 'unread');
1250 1250 var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0];
1251 1251
1252 1252 if(r_button.parentNode !== undefined){
1253 1253 r_button.parentNode.removeChild(r_button);
1254 1254 }
1255 1255 _run_callbacks(callbacks);
1256 1256 },
1257 1257 failure:function(o){
1258 1258 alert("error");
1259 1259 },
1260 1260 };
1261 1261 var postData = '_method=put';
1262 1262 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1263 1263 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
1264 1264 callback, postData);
1265 1265 };
1266 1266
1267 1267 /** MEMBERS AUTOCOMPLETE WIDGET **/
1268 1268
1269 1269 var MembersAutoComplete = function (divid, cont, users_list, groups_list) {
1270 1270 var myUsers = users_list;
1271 1271 var myGroups = groups_list;
1272 1272
1273 1273 // Define a custom search function for the DataSource of users
1274 1274 var matchUsers = function (sQuery) {
1275 1275 // Case insensitive matching
1276 1276 var query = sQuery.toLowerCase();
1277 1277 var i = 0;
1278 1278 var l = myUsers.length;
1279 1279 var matches = [];
1280 1280
1281 1281 // Match against each name of each contact
1282 1282 for (; i < l; i++) {
1283 1283 contact = myUsers[i];
1284 1284 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1285 1285 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1286 1286 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1287 1287 matches[matches.length] = contact;
1288 1288 }
1289 1289 }
1290 1290 return matches;
1291 1291 };
1292 1292
1293 1293 // Define a custom search function for the DataSource of userGroups
1294 1294 var matchGroups = function (sQuery) {
1295 1295 // Case insensitive matching
1296 1296 var query = sQuery.toLowerCase();
1297 1297 var i = 0;
1298 1298 var l = myGroups.length;
1299 1299 var matches = [];
1300 1300
1301 1301 // Match against each name of each contact
1302 1302 for (; i < l; i++) {
1303 1303 matched_group = myGroups[i];
1304 1304 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1305 1305 matches[matches.length] = matched_group;
1306 1306 }
1307 1307 }
1308 1308 return matches;
1309 1309 };
1310 1310
1311 1311 //match all
1312 1312 var matchAll = function (sQuery) {
1313 1313 u = matchUsers(sQuery);
1314 1314 g = matchGroups(sQuery);
1315 1315 return u.concat(g);
1316 1316 };
1317 1317
1318 1318 // DataScheme for members
1319 1319 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
1320 1320 memberDS.responseSchema = {
1321 1321 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
1322 1322 };
1323 1323
1324 1324 // DataScheme for owner
1325 1325 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1326 1326 ownerDS.responseSchema = {
1327 1327 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1328 1328 };
1329 1329
1330 1330 // Instantiate AutoComplete for perms
1331 1331 var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS);
1332 1332 membersAC.useShadow = false;
1333 1333 membersAC.resultTypeList = false;
1334 1334 membersAC.animVert = false;
1335 1335 membersAC.animHoriz = false;
1336 1336 membersAC.animSpeed = 0.1;
1337 1337
1338 1338 // Instantiate AutoComplete for owner
1339 1339 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
1340 1340 ownerAC.useShadow = false;
1341 1341 ownerAC.resultTypeList = false;
1342 1342 ownerAC.animVert = false;
1343 1343 ownerAC.animHoriz = false;
1344 1344 ownerAC.animSpeed = 0.1;
1345 1345
1346 1346 // Helper highlight function for the formatter
1347 1347 var highlightMatch = function (full, snippet, matchindex) {
1348 1348 return full.substring(0, matchindex)
1349 1349 + "<span class='match'>"
1350 1350 + full.substr(matchindex, snippet.length)
1351 1351 + "</span>" + full.substring(matchindex + snippet.length);
1352 1352 };
1353 1353
1354 1354 // Custom formatter to highlight the matching letters
1355 1355 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
1356 1356 var query = sQuery.toLowerCase();
1357 1357 var _gravatar = function(res, em, group){
1358 1358 if (group !== undefined){
1359 1359 em = '/images/icons/group.png'
1360 1360 }
1361 1361 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1362 1362 return tmpl.format(em,res)
1363 1363 }
1364 1364 // group
1365 1365 if (oResultData.grname != undefined) {
1366 1366 var grname = oResultData.grname;
1367 1367 var grmembers = oResultData.grmembers;
1368 1368 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1369 1369 var grprefix = "{0}: ".format(_TM['Group']);
1370 1370 var grsuffix = " (" + grmembers + " )";
1371 1371 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1372 1372
1373 1373 if (grnameMatchIndex > -1) {
1374 1374 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
1375 1375 }
1376 1376 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
1377 1377 // Users
1378 1378 } else if (oResultData.nname != undefined) {
1379 1379 var fname = oResultData.fname || "";
1380 1380 var lname = oResultData.lname || "";
1381 1381 var nname = oResultData.nname;
1382 1382
1383 1383 // Guard against null value
1384 1384 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1385 1385 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1386 1386 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1387 1387 displayfname, displaylname, displaynname;
1388 1388
1389 1389 if (fnameMatchIndex > -1) {
1390 1390 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1391 1391 } else {
1392 1392 displayfname = fname;
1393 1393 }
1394 1394
1395 1395 if (lnameMatchIndex > -1) {
1396 1396 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1397 1397 } else {
1398 1398 displaylname = lname;
1399 1399 }
1400 1400
1401 1401 if (nnameMatchIndex > -1) {
1402 1402 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1403 1403 } else {
1404 1404 displaynname = nname ? "(" + nname + ")" : "";
1405 1405 }
1406 1406
1407 1407 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1408 1408 } else {
1409 1409 return '';
1410 1410 }
1411 1411 };
1412 1412 membersAC.formatResult = custom_formatter;
1413 1413 ownerAC.formatResult = custom_formatter;
1414 1414
1415 1415 var myHandler = function (sType, aArgs) {
1416 1416 var nextId = divid.split('perm_new_member_name_')[1];
1417 1417 var myAC = aArgs[0]; // reference back to the AC instance
1418 1418 var elLI = aArgs[1]; // reference to the selected LI element
1419 1419 var oData = aArgs[2]; // object literal of selected item's result data
1420 1420 //fill the autocomplete with value
1421 1421 if (oData.nname != undefined) {
1422 1422 //users
1423 1423 myAC.getInputEl().value = oData.nname;
1424 1424 YUD.get('perm_new_member_type_'+nextId).value = 'user';
1425 1425 } else {
1426 1426 //groups
1427 1427 myAC.getInputEl().value = oData.grname;
1428 1428 YUD.get('perm_new_member_type_'+nextId).value = 'users_group';
1429 1429 }
1430 1430 };
1431 1431
1432 1432 membersAC.itemSelectEvent.subscribe(myHandler);
1433 1433 if(ownerAC.itemSelectEvent){
1434 1434 ownerAC.itemSelectEvent.subscribe(myHandler);
1435 1435 }
1436 1436
1437 1437 return {
1438 1438 memberDS: memberDS,
1439 1439 ownerDS: ownerDS,
1440 1440 membersAC: membersAC,
1441 1441 ownerAC: ownerAC,
1442 1442 };
1443 1443 }
1444 1444
1445 1445
1446 1446 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1447 1447 var myUsers = users_list;
1448 1448 var myGroups = groups_list;
1449 1449
1450 1450 // Define a custom search function for the DataSource of users
1451 1451 var matchUsers = function (sQuery) {
1452 1452 var org_sQuery = sQuery;
1453 1453 if(this.mentionQuery == null){
1454 1454 return []
1455 1455 }
1456 1456 sQuery = this.mentionQuery;
1457 1457 // Case insensitive matching
1458 1458 var query = sQuery.toLowerCase();
1459 1459 var i = 0;
1460 1460 var l = myUsers.length;
1461 1461 var matches = [];
1462 1462
1463 1463 // Match against each name of each contact
1464 1464 for (; i < l; i++) {
1465 1465 contact = myUsers[i];
1466 1466 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1467 1467 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1468 1468 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1469 1469 matches[matches.length] = contact;
1470 1470 }
1471 1471 }
1472 1472 return matches
1473 1473 };
1474 1474
1475 1475 //match all
1476 1476 var matchAll = function (sQuery) {
1477 1477 u = matchUsers(sQuery);
1478 1478 return u
1479 1479 };
1480 1480
1481 1481 // DataScheme for owner
1482 1482 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1483 1483
1484 1484 ownerDS.responseSchema = {
1485 1485 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1486 1486 };
1487 1487
1488 1488 // Instantiate AutoComplete for mentions
1489 1489 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1490 1490 ownerAC.useShadow = false;
1491 1491 ownerAC.resultTypeList = false;
1492 1492 ownerAC.suppressInputUpdate = true;
1493 1493 ownerAC.animVert = false;
1494 1494 ownerAC.animHoriz = false;
1495 1495 ownerAC.animSpeed = 0.1;
1496 1496
1497 1497 // Helper highlight function for the formatter
1498 1498 var highlightMatch = function (full, snippet, matchindex) {
1499 1499 return full.substring(0, matchindex)
1500 1500 + "<span class='match'>"
1501 1501 + full.substr(matchindex, snippet.length)
1502 1502 + "</span>" + full.substring(matchindex + snippet.length);
1503 1503 };
1504 1504
1505 1505 // Custom formatter to highlight the matching letters
1506 1506 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1507 1507 var org_sQuery = sQuery;
1508 1508 if(this.dataSource.mentionQuery != null){
1509 1509 sQuery = this.dataSource.mentionQuery;
1510 1510 }
1511 1511
1512 1512 var query = sQuery.toLowerCase();
1513 1513 var _gravatar = function(res, em, group){
1514 1514 if (group !== undefined){
1515 1515 em = '/images/icons/group.png'
1516 1516 }
1517 1517 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1518 1518 return tmpl.format(em,res)
1519 1519 }
1520 1520 if (oResultData.nname != undefined) {
1521 1521 var fname = oResultData.fname || "";
1522 1522 var lname = oResultData.lname || "";
1523 1523 var nname = oResultData.nname;
1524 1524
1525 1525 // Guard against null value
1526 1526 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1527 1527 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1528 1528 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1529 1529 displayfname, displaylname, displaynname;
1530 1530
1531 1531 if (fnameMatchIndex > -1) {
1532 1532 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1533 1533 } else {
1534 1534 displayfname = fname;
1535 1535 }
1536 1536
1537 1537 if (lnameMatchIndex > -1) {
1538 1538 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1539 1539 } else {
1540 1540 displaylname = lname;
1541 1541 }
1542 1542
1543 1543 if (nnameMatchIndex > -1) {
1544 1544 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1545 1545 } else {
1546 1546 displaynname = nname ? "(" + nname + ")" : "";
1547 1547 }
1548 1548
1549 1549 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1550 1550 } else {
1551 1551 return '';
1552 1552 }
1553 1553 };
1554 1554
1555 1555 if(ownerAC.itemSelectEvent){
1556 1556 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1557 1557
1558 1558 var myAC = aArgs[0]; // reference back to the AC instance
1559 1559 var elLI = aArgs[1]; // reference to the selected LI element
1560 1560 var oData = aArgs[2]; // object literal of selected item's result data
1561 1561 //fill the autocomplete with value
1562 1562 if (oData.nname != undefined) {
1563 1563 //users
1564 1564 //Replace the mention name with replaced
1565 1565 var re = new RegExp();
1566 1566 var org = myAC.getInputEl().value;
1567 1567 var chunks = myAC.dataSource.chunks
1568 1568 // replace middle chunk(the search term) with actuall match
1569 1569 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1570 1570 '@'+oData.nname+' ');
1571 1571 myAC.getInputEl().value = chunks.join('')
1572 1572 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1573 1573 } else {
1574 1574 //groups
1575 1575 myAC.getInputEl().value = oData.grname;
1576 1576 YUD.get('perm_new_member_type').value = 'users_group';
1577 1577 }
1578 1578 });
1579 1579 }
1580 1580
1581 1581 // in this keybuffer we will gather current value of search !
1582 1582 // since we need to get this just when someone does `@` then we do the
1583 1583 // search
1584 1584 ownerAC.dataSource.chunks = [];
1585 1585 ownerAC.dataSource.mentionQuery = null;
1586 1586
1587 1587 ownerAC.get_mention = function(msg, max_pos) {
1588 1588 var org = msg;
1589 1589 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1590 1590 var chunks = [];
1591 1591
1592 1592
1593 1593 // cut first chunk until curret pos
1594 1594 var to_max = msg.substr(0, max_pos);
1595 1595 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1596 1596 var msg2 = to_max.substr(at_pos);
1597 1597
1598 1598 chunks.push(org.substr(0,at_pos))// prefix chunk
1599 1599 chunks.push(msg2) // search chunk
1600 1600 chunks.push(org.substr(max_pos)) // postfix chunk
1601 1601
1602 1602 // clean up msg2 for filtering and regex match
1603 1603 var msg2 = msg2.lstrip(' ').lstrip('\n');
1604 1604
1605 1605 if(re.test(msg2)){
1606 1606 var unam = re.exec(msg2)[1];
1607 1607 return [unam, chunks];
1608 1608 }
1609 1609 return [null, null];
1610 1610 };
1611 1611
1612 1612 if (ownerAC.textboxKeyUpEvent){
1613 1613 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1614 1614
1615 1615 var ac_obj = args[0];
1616 1616 var currentMessage = args[1];
1617 1617 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1618 1618
1619 1619 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1620 1620 var curr_search = null;
1621 1621 if(unam[0]){
1622 1622 curr_search = unam[0];
1623 1623 }
1624 1624
1625 1625 ownerAC.dataSource.chunks = unam[1];
1626 1626 ownerAC.dataSource.mentionQuery = curr_search;
1627 1627
1628 1628 })
1629 1629 }
1630 1630 return {
1631 1631 ownerDS: ownerDS,
1632 1632 ownerAC: ownerAC,
1633 1633 };
1634 1634 }
1635 1635
1636 1636 var addReviewMember = function(id,fname,lname,nname,gravatar_link){
1637 1637 var members = YUD.get('review_members');
1638 1638 var tmpl = '<li id="reviewer_{2}">'+
1639 1639 '<div class="reviewers_member">'+
1640 1640 '<div class="gravatar"><img alt="gravatar" src="{0}"/> </div>'+
1641 1641 '<div style="float:left">{1}</div>'+
1642 1642 '<input type="hidden" value="{2}" name="review_members" />'+
1643 1643 '<span class="delete_icon action_button" onclick="removeReviewMember({2})"></span>'+
1644 1644 '</div>'+
1645 1645 '</li>' ;
1646 1646 var displayname = "{0} {1} ({2})".format(fname,lname,nname);
1647 1647 var element = tmpl.format(gravatar_link,displayname,id);
1648 1648 // check if we don't have this ID already in
1649 1649 var ids = [];
1650 1650 var _els = YUQ('#review_members li');
1651 1651 for (el in _els){
1652 1652 ids.push(_els[el].id)
1653 1653 }
1654 1654 if(ids.indexOf('reviewer_'+id) == -1){
1655 1655 //only add if it's not there
1656 1656 members.innerHTML += element;
1657 1657 }
1658 1658
1659 1659 }
1660 1660
1661 1661 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1662 1662 var el = YUD.get('reviewer_{0}'.format(reviewer_id));
1663 1663 if (el.parentNode !== undefined){
1664 1664 el.parentNode.removeChild(el);
1665 1665 }
1666 1666 }
1667 1667
1668 1668 var updateReviewers = function(reviewers_ids, repo_name, pull_request_id){
1669 1669 if (reviewers_ids === undefined){
1670 1670 var reviewers_ids = [];
1671 1671 var ids = YUQ('#review_members input');
1672 1672 for(var i=0; i<ids.length;i++){
1673 1673 var id = ids[i].value
1674 1674 reviewers_ids.push(id);
1675 1675 }
1676 1676 }
1677 1677 var url = pyroutes.url('pullrequest_update', {"repo_name":repo_name,
1678 1678 "pull_request_id": pull_request_id});
1679 1679 var postData = {'_method':'put',
1680 1680 'reviewers_ids': reviewers_ids};
1681 1681 var success = function(o){
1682 1682 window.location.reload();
1683 1683 }
1684 1684 ajaxPOST(url,postData,success);
1685 1685 }
1686 1686
1687 1687 var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) {
1688 1688 var myUsers = users_list;
1689 1689 var myGroups = groups_list;
1690 1690
1691 1691 // Define a custom search function for the DataSource of users
1692 1692 var matchUsers = function (sQuery) {
1693 1693 // Case insensitive matching
1694 1694 var query = sQuery.toLowerCase();
1695 1695 var i = 0;
1696 1696 var l = myUsers.length;
1697 1697 var matches = [];
1698 1698
1699 1699 // Match against each name of each contact
1700 1700 for (; i < l; i++) {
1701 1701 contact = myUsers[i];
1702 1702 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1703 1703 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1704 1704 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1705 1705 matches[matches.length] = contact;
1706 1706 }
1707 1707 }
1708 1708 return matches;
1709 1709 };
1710 1710
1711 1711 // Define a custom search function for the DataSource of userGroups
1712 1712 var matchGroups = function (sQuery) {
1713 1713 // Case insensitive matching
1714 1714 var query = sQuery.toLowerCase();
1715 1715 var i = 0;
1716 1716 var l = myGroups.length;
1717 1717 var matches = [];
1718 1718
1719 1719 // Match against each name of each contact
1720 1720 for (; i < l; i++) {
1721 1721 matched_group = myGroups[i];
1722 1722 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1723 1723 matches[matches.length] = matched_group;
1724 1724 }
1725 1725 }
1726 1726 return matches;
1727 1727 };
1728 1728
1729 1729 //match all
1730 1730 var matchAll = function (sQuery) {
1731 1731 u = matchUsers(sQuery);
1732 1732 return u
1733 1733 };
1734 1734
1735 1735 // DataScheme for owner
1736 1736 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1737 1737
1738 1738 ownerDS.responseSchema = {
1739 1739 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1740 1740 };
1741 1741
1742 1742 // Instantiate AutoComplete for mentions
1743 1743 var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1744 1744 reviewerAC.useShadow = false;
1745 1745 reviewerAC.resultTypeList = false;
1746 1746 reviewerAC.suppressInputUpdate = true;
1747 1747 reviewerAC.animVert = false;
1748 1748 reviewerAC.animHoriz = false;
1749 1749 reviewerAC.animSpeed = 0.1;
1750 1750
1751 1751 // Helper highlight function for the formatter
1752 1752 var highlightMatch = function (full, snippet, matchindex) {
1753 1753 return full.substring(0, matchindex)
1754 1754 + "<span class='match'>"
1755 1755 + full.substr(matchindex, snippet.length)
1756 1756 + "</span>" + full.substring(matchindex + snippet.length);
1757 1757 };
1758 1758
1759 1759 // Custom formatter to highlight the matching letters
1760 1760 reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1761 1761 var org_sQuery = sQuery;
1762 1762 if(this.dataSource.mentionQuery != null){
1763 1763 sQuery = this.dataSource.mentionQuery;
1764 1764 }
1765 1765
1766 1766 var query = sQuery.toLowerCase();
1767 1767 var _gravatar = function(res, em, group){
1768 1768 if (group !== undefined){
1769 1769 em = '/images/icons/group.png'
1770 1770 }
1771 1771 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1772 1772 return tmpl.format(em,res)
1773 1773 }
1774 1774 if (oResultData.nname != undefined) {
1775 1775 var fname = oResultData.fname || "";
1776 1776 var lname = oResultData.lname || "";
1777 1777 var nname = oResultData.nname;
1778 1778
1779 1779 // Guard against null value
1780 1780 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1781 1781 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1782 1782 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1783 1783 displayfname, displaylname, displaynname;
1784 1784
1785 1785 if (fnameMatchIndex > -1) {
1786 1786 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1787 1787 } else {
1788 1788 displayfname = fname;
1789 1789 }
1790 1790
1791 1791 if (lnameMatchIndex > -1) {
1792 1792 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1793 1793 } else {
1794 1794 displaylname = lname;
1795 1795 }
1796 1796
1797 1797 if (nnameMatchIndex > -1) {
1798 1798 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1799 1799 } else {
1800 1800 displaynname = nname ? "(" + nname + ")" : "";
1801 1801 }
1802 1802
1803 1803 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1804 1804 } else {
1805 1805 return '';
1806 1806 }
1807 1807 };
1808 1808
1809 1809 //members cache to catch duplicates
1810 1810 reviewerAC.dataSource.cache = [];
1811 1811 // hack into select event
1812 1812 if(reviewerAC.itemSelectEvent){
1813 1813 reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1814 1814
1815 1815 var myAC = aArgs[0]; // reference back to the AC instance
1816 1816 var elLI = aArgs[1]; // reference to the selected LI element
1817 1817 var oData = aArgs[2]; // object literal of selected item's result data
1818 1818
1819 1819 //fill the autocomplete with value
1820 1820
1821 1821 if (oData.nname != undefined) {
1822 1822 addReviewMember(oData.id, oData.fname, oData.lname, oData.nname,
1823 1823 oData.gravatar_lnk);
1824 1824 myAC.dataSource.cache.push(oData.id);
1825 1825 YUD.get('user').value = ''
1826 1826 }
1827 1827 });
1828 1828 }
1829 1829 return {
1830 1830 ownerDS: ownerDS,
1831 1831 reviewerAC: reviewerAC,
1832 1832 };
1833 1833 }
1834 1834
1835 1835 /**
1836 1836 * QUICK REPO MENU
1837 1837 */
1838 1838 var quick_repo_menu = function(){
1839 1839 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1840 1840 var menu = e.currentTarget.firstElementChild.firstElementChild;
1841 1841 if(YUD.hasClass(menu,'hidden')){
1842 1842 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1843 1843 YUD.replaceClass(menu, 'hidden', 'active');
1844 1844 }
1845 1845 })
1846 1846 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1847 1847 var menu = e.currentTarget.firstElementChild.firstElementChild;
1848 1848 if(YUD.hasClass(menu,'active')){
1849 1849 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1850 1850 YUD.replaceClass(menu, 'active', 'hidden');
1851 1851 }
1852 1852 })
1853 1853 };
1854 1854
1855 1855
1856 1856 /**
1857 1857 * TABLE SORTING
1858 1858 */
1859 1859
1860 1860 // returns a node from given html;
1861 1861 var fromHTML = function(html){
1862 1862 var _html = document.createElement('element');
1863 1863 _html.innerHTML = html;
1864 1864 return _html;
1865 1865 }
1866 1866 var get_rev = function(node){
1867 1867 var n = node.firstElementChild.firstElementChild;
1868 1868
1869 1869 if (n===null){
1870 1870 return -1
1871 1871 }
1872 1872 else{
1873 1873 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1874 1874 return parseInt(out);
1875 1875 }
1876 1876 }
1877 1877
1878 1878 var get_name = function(node){
1879 1879 var name = node.firstElementChild.children[2].innerHTML;
1880 1880 return name
1881 1881 }
1882 1882 var get_group_name = function(node){
1883 1883 var name = node.firstElementChild.children[1].innerHTML;
1884 1884 return name
1885 1885 }
1886 1886 var get_date = function(node){
1887 1887 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1888 1888 return date_
1889 1889 }
1890 1890
1891 1891 var get_age = function(node){
1892 1892 return node
1893 1893 }
1894 1894
1895 1895 var get_link = function(node){
1896 1896 return node.firstElementChild.text;
1897 1897 }
1898 1898
1899 1899 var revisionSort = function(a, b, desc, field) {
1900 1900
1901 1901 var a_ = fromHTML(a.getData(field));
1902 1902 var b_ = fromHTML(b.getData(field));
1903 1903
1904 1904 // extract revisions from string nodes
1905 1905 a_ = get_rev(a_)
1906 1906 b_ = get_rev(b_)
1907 1907
1908 1908 var comp = YAHOO.util.Sort.compare;
1909 1909 var compState = comp(a_, b_, desc);
1910 1910 return compState;
1911 1911 };
1912 1912 var ageSort = function(a, b, desc, field) {
1913 1913 var a_ = fromHTML(a.getData(field));
1914 1914 var b_ = fromHTML(b.getData(field));
1915 1915
1916 1916 // extract name from table
1917 1917 a_ = get_date(a_)
1918 1918 b_ = get_date(b_)
1919 1919
1920 1920 var comp = YAHOO.util.Sort.compare;
1921 1921 var compState = comp(a_, b_, desc);
1922 1922 return compState;
1923 1923 };
1924 1924
1925 1925 var lastLoginSort = function(a, b, desc, field) {
1926 1926 var a_ = a.getData('last_login_raw') || 0;
1927 1927 var b_ = b.getData('last_login_raw') || 0;
1928 1928
1929 1929 var comp = YAHOO.util.Sort.compare;
1930 1930 var compState = comp(a_, b_, desc);
1931 1931 return compState;
1932 1932 };
1933 1933
1934 1934 var nameSort = function(a, b, desc, field) {
1935 1935 var a_ = fromHTML(a.getData(field));
1936 1936 var b_ = fromHTML(b.getData(field));
1937 1937
1938 1938 // extract name from table
1939 1939 a_ = get_name(a_)
1940 1940 b_ = get_name(b_)
1941 1941
1942 1942 var comp = YAHOO.util.Sort.compare;
1943 1943 var compState = comp(a_, b_, desc);
1944 1944 return compState;
1945 1945 };
1946 1946
1947 1947 var permNameSort = function(a, b, desc, field) {
1948 1948 var a_ = fromHTML(a.getData(field));
1949 1949 var b_ = fromHTML(b.getData(field));
1950 1950 // extract name from table
1951 1951
1952 1952 a_ = a_.children[0].innerHTML;
1953 1953 b_ = b_.children[0].innerHTML;
1954 1954
1955 1955 var comp = YAHOO.util.Sort.compare;
1956 1956 var compState = comp(a_, b_, desc);
1957 1957 return compState;
1958 1958 };
1959 1959
1960 1960 var groupNameSort = function(a, b, desc, field) {
1961 1961 var a_ = fromHTML(a.getData(field));
1962 1962 var b_ = fromHTML(b.getData(field));
1963 1963
1964 1964 // extract name from table
1965 1965 a_ = get_group_name(a_)
1966 1966 b_ = get_group_name(b_)
1967 1967
1968 1968 var comp = YAHOO.util.Sort.compare;
1969 1969 var compState = comp(a_, b_, desc);
1970 1970 return compState;
1971 1971 };
1972 1972 var dateSort = function(a, b, desc, field) {
1973 1973 var a_ = fromHTML(a.getData(field));
1974 1974 var b_ = fromHTML(b.getData(field));
1975 1975
1976 1976 // extract name from table
1977 1977 a_ = get_date(a_)
1978 1978 b_ = get_date(b_)
1979 1979
1980 1980 var comp = YAHOO.util.Sort.compare;
1981 1981 var compState = comp(a_, b_, desc);
1982 1982 return compState;
1983 1983 };
1984 1984
1985 1985 var linkSort = function(a, b, desc, field) {
1986 1986 var a_ = fromHTML(a.getData(field));
1987 1987 var b_ = fromHTML(a.getData(field));
1988 1988
1989 1989 // extract url text from string nodes
1990 1990 a_ = get_link(a_)
1991 1991 b_ = get_link(b_)
1992 1992
1993 1993 var comp = YAHOO.util.Sort.compare;
1994 1994 var compState = comp(a_, b_, desc);
1995 1995 return compState;
1996 1996 }
1997 1997
1998 1998 var addPermAction = function(_html, users_list, groups_list){
1999 1999 var elmts = YUD.getElementsByClassName('last_new_member');
2000 2000 var last_node = elmts[elmts.length-1];
2001 2001 if (last_node){
2002 2002 var next_id = (YUD.getElementsByClassName('new_members')).length;
2003 2003 _html = _html.format(next_id);
2004 2004 last_node.innerHTML = _html;
2005 2005 YUD.setStyle(last_node, 'display', '');
2006 2006 YUD.removeClass(last_node, 'last_new_member');
2007 2007 MembersAutoComplete("perm_new_member_name_"+next_id,
2008 2008 "perm_container_"+next_id, users_list, groups_list);
2009 2009 //create new last NODE
2010 2010 var el = document.createElement('tr');
2011 2011 el.id = 'add_perm_input';
2012 2012 YUD.addClass(el,'last_new_member');
2013 2013 YUD.addClass(el,'new_members');
2014 2014 YUD.insertAfter(el, last_node);
2015 2015 }
2016 2016 }
2017 2017
2018 2018 /* Multi selectors */
2019 2019
2020 2020 var MultiSelectWidget = function(selected_id, available_id, form_id){
2021 2021
2022 2022
2023 2023 //definition of containers ID's
2024 2024 var selected_container = selected_id;
2025 2025 var available_container = available_id;
2026 2026
2027 2027 //temp container for selected storage.
2028 2028 var cache = new Array();
2029 2029 var av_cache = new Array();
2030 2030 var c = YUD.get(selected_container);
2031 2031 var ac = YUD.get(available_container);
2032 2032
2033 2033 //get only selected options for further fullfilment
2034 2034 for(var i = 0;node =c.options[i];i++){
2035 2035 if(node.selected){
2036 2036 //push selected to my temp storage left overs :)
2037 2037 cache.push(node);
2038 2038 }
2039 2039 }
2040 2040
2041 2041 //get all available options to cache
2042 2042 for(var i = 0;node =ac.options[i];i++){
2043 2043 //push selected to my temp storage left overs :)
2044 2044 av_cache.push(node);
2045 2045 }
2046 2046
2047 //fill available only with those not in choosen
2047 //fill available only with those not in chosen
2048 2048 ac.options.length=0;
2049 2049 tmp_cache = new Array();
2050 2050
2051 2051 for(var i = 0;node = av_cache[i];i++){
2052 2052 var add = true;
2053 2053 for(var i2 = 0;node_2 = cache[i2];i2++){
2054 2054 if(node.value == node_2.value){
2055 2055 add=false;
2056 2056 break;
2057 2057 }
2058 2058 }
2059 2059 if(add){
2060 2060 tmp_cache.push(new Option(node.text, node.value, false, false));
2061 2061 }
2062 2062 }
2063 2063
2064 2064 for(var i = 0;node = tmp_cache[i];i++){
2065 2065 ac.options[i] = node;
2066 2066 }
2067 2067
2068 2068 function prompts_action_callback(e){
2069 2069
2070 var choosen = YUD.get(selected_container);
2070 var chosen = YUD.get(selected_container);
2071 2071 var available = YUD.get(available_container);
2072 2072
2073 2073 //get checked and unchecked options from field
2074 2074 function get_checked(from_field){
2075 2075 //temp container for storage.
2076 2076 var sel_cache = new Array();
2077 2077 var oth_cache = new Array();
2078 2078
2079 2079 for(var i = 0;node = from_field.options[i];i++){
2080 2080 if(node.selected){
2081 2081 //push selected fields :)
2082 2082 sel_cache.push(node);
2083 2083 }
2084 2084 else{
2085 2085 oth_cache.push(node)
2086 2086 }
2087 2087 }
2088 2088
2089 2089 return [sel_cache,oth_cache]
2090 2090 }
2091 2091
2092 2092 //fill the field with given options
2093 2093 function fill_with(field,options){
2094 2094 //clear firtst
2095 2095 field.options.length=0;
2096 2096 for(var i = 0;node = options[i];i++){
2097 2097 field.options[i]=new Option(node.text, node.value,
2098 2098 false, false);
2099 2099 }
2100 2100
2101 2101 }
2102 2102 //adds to current field
2103 2103 function add_to(field,options){
2104 2104 for(var i = 0;node = options[i];i++){
2105 2105 field.appendChild(new Option(node.text, node.value,
2106 2106 false, false));
2107 2107 }
2108 2108 }
2109 2109
2110 2110 // add action
2111 2111 if (this.id=='add_element'){
2112 2112 var c = get_checked(available);
2113 add_to(choosen,c[0]);
2113 add_to(chosen,c[0]);
2114 2114 fill_with(available,c[1]);
2115 2115 }
2116 2116 // remove action
2117 2117 if (this.id=='remove_element'){
2118 var c = get_checked(choosen);
2118 var c = get_checked(chosen);
2119 2119 add_to(available,c[0]);
2120 fill_with(choosen,c[1]);
2120 fill_with(chosen,c[1]);
2121 2121 }
2122 2122 // add all elements
2123 2123 if(this.id=='add_all_elements'){
2124 2124 for(var i=0; node = available.options[i];i++){
2125 choosen.appendChild(new Option(node.text,
2125 chosen.appendChild(new Option(node.text,
2126 2126 node.value, false, false));
2127 2127 }
2128 2128 available.options.length = 0;
2129 2129 }
2130 2130 //remove all elements
2131 2131 if(this.id=='remove_all_elements'){
2132 for(var i=0; node = choosen.options[i];i++){
2132 for(var i=0; node = chosen.options[i];i++){
2133 2133 available.appendChild(new Option(node.text,
2134 2134 node.value, false, false));
2135 2135 }
2136 choosen.options.length = 0;
2136 chosen.options.length = 0;
2137 2137 }
2138 2138
2139 2139 }
2140 2140
2141 2141 YUE.addListener(['add_element','remove_element',
2142 2142 'add_all_elements','remove_all_elements'],'click',
2143 2143 prompts_action_callback)
2144 2144 if (form_id !== undefined) {
2145 2145 YUE.addListener(form_id,'submit',function(){
2146 var choosen = YUD.get(selected_container);
2147 for (var i = 0; i < choosen.options.length; i++) {
2148 choosen.options[i].selected = 'selected';
2146 var chosen = YUD.get(selected_container);
2147 for (var i = 0; i < chosen.options.length; i++) {
2148 chosen.options[i].selected = 'selected';
2149 2149 }
2150 2150 });
2151 2151 }
2152 2152 }
2153 2153
2154 2154
2155 2155 // global hooks after DOM is loaded
2156 2156
2157 2157 YUE.onDOMReady(function(){
2158 2158 YUE.on(YUQ('.diff-collapse-button'), 'click', function(e){
2159 2159 var button = e.currentTarget;
2160 2160 var t = YUD.get(button).getAttribute('target');
2161 2161 console.log(t);
2162 2162 if(YUD.hasClass(t, 'hidden')){
2163 2163 YUD.removeClass(t, 'hidden');
2164 2164 YUD.get(button).innerHTML = "&uarr; {0} &uarr;".format(_TM['Collapse diff']);
2165 2165 }
2166 2166 else if(!YUD.hasClass(t, 'hidden')){
2167 2167 YUD.addClass(t, 'hidden');
2168 2168 YUD.get(button).innerHTML = "&darr; {0} &darr;".format(_TM['Expand diff']);
2169 2169 }
2170 2170 });
2171 2171
2172 2172
2173 2173
2174 2174 });
2175 2175
@@ -1,215 +1,215 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Permissions administration')} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${_('permissions')}
12 12 </%def>
13 13
14 14 <%def name="page_nav()">
15 15 ${self.menu('admin')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box box-left">
20 20 <!-- box / title -->
21 21 <div class="title">
22 22 ${self.breadcrumbs()}
23 23 </div>
24 24 <h3>${_('Default permissions')}</h3>
25 25 ${h.form(url('permission', id='default'),method='put')}
26 26 <div class="form">
27 27 <!-- fields -->
28 28 <div class="fields">
29 29 <div class="field">
30 30 <div class="label label-checkbox">
31 31 <label for="anonymous">${_('Anonymous access')}:</label>
32 32 </div>
33 33 <div class="checkboxes">
34 34 <div class="checkbox">
35 35 ${h.checkbox('anonymous',True)}
36 36 </div>
37 37 </div>
38 38 </div>
39 39 <div class="field">
40 40 <div class="label">
41 41 <label for="default_repo_perm">${_('Repository')}:</label>
42 42 </div>
43 43 <div class="select">
44 44 ${h.select('default_repo_perm','',c.repo_perms_choices)}
45 45
46 46 ${h.checkbox('overwrite_default_repo','true')}
47 47 <label for="overwrite_default_repo">
48 48 <span class="tooltip"
49 title="${h.tooltip(_('All default permissions on each repository will be reset to choosen permission, note that all custom default permission on repositories will be lost'))}">
49 title="${h.tooltip(_('All default permissions on each repository will be reset to chosen permission, note that all custom default permission on repositories will be lost'))}">
50 50 ${_('overwrite existing settings')}</span> </label>
51 51 </div>
52 52 </div>
53 53 <div class="field">
54 54 <div class="label">
55 55 <label for="default_group_perm">${_('Repository group')}:</label>
56 56 </div>
57 57 <div class="select">
58 58 ${h.select('default_group_perm','',c.group_perms_choices)}
59 59 ${h.checkbox('overwrite_default_group','true')}
60 60 <label for="overwrite_default_group">
61 61 <span class="tooltip"
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to choosen permission, note that all custom default permission on repository groups will be lost'))}">
62 title="${h.tooltip(_('All default permissions on each repository group will be reset to chosen permission, note that all custom default permission on repository groups will be lost'))}">
63 63 ${_('overwrite existing settings')}</span> </label>
64 64
65 65 </div>
66 66 </div>
67 67 <div class="field">
68 68 <div class="label">
69 69 <label for="default_register">${_('Registration')}:</label>
70 70 </div>
71 71 <div class="select">
72 72 ${h.select('default_register','',c.register_choices)}
73 73 </div>
74 74 </div>
75 75 <div class="field">
76 76 <div class="label">
77 77 <label for="default_create">${_('Repository creation')}:</label>
78 78 </div>
79 79 <div class="select">
80 80 ${h.select('default_create','',c.create_choices)}
81 81 </div>
82 82 </div>
83 83 <div class="field">
84 84 <div class="label">
85 85 <label for="default_fork">${_('Repository forking')}:</label>
86 86 </div>
87 87 <div class="select">
88 88 ${h.select('default_fork','',c.fork_choices)}
89 89 </div>
90 90 </div>
91 91 <div class="buttons">
92 92 ${h.submit('save',_('Save'),class_="ui-btn large")}
93 93 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
94 94 </div>
95 95 </div>
96 96 </div>
97 97 ${h.end_form()}
98 98 </div>
99 99
100 100 <div style="min-height:780px" class="box box-right">
101 101 <!-- box / title -->
102 102 <div class="title">
103 103 <h5>${_('Default User Permissions')}</h5>
104 104 </div>
105 105
106 106 ## permissions overview
107 107 <div id="perms" class="table">
108 108 %for section in sorted(c.perm_user.permissions.keys()):
109 109 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
110 110 %if not c.perm_user.permissions[section]:
111 111 <span class="empty_data">${_('Nothing here yet')}</span>
112 112 %else:
113 113 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
114 114 <table id="tbl_list_${section}">
115 115 <thead>
116 116 <tr>
117 117 <th class="left">${_('Name')}</th>
118 118 <th class="left">${_('Permission')}</th>
119 119 <th class="left">${_('Edit Permission')}</th>
120 120 </thead>
121 121 <tbody>
122 122 %for k in c.perm_user.permissions[section]:
123 123 <%
124 124 if section != 'global':
125 125 section_perm = c.perm_user.permissions[section].get(k)
126 126 _perm = section_perm.split('.')[-1]
127 127 else:
128 128 _perm = section_perm = None
129 129 %>
130 130 <tr>
131 131 <td>
132 132 %if section == 'repositories':
133 133 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
134 134 %elif section == 'repositories_groups':
135 135 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
136 136 %else:
137 137 ${h.get_permission_name(k)}
138 138 %endif
139 139 </td>
140 140 <td>
141 141 %if section == 'global':
142 142 ${h.bool2icon(k.split('.')[-1] != 'none')}
143 143 %else:
144 144 <span class="perm_tag ${_perm}">${section_perm}</span>
145 145 %endif
146 146 </td>
147 147 <td>
148 148 %if section == 'repositories':
149 149 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
150 150 %elif section == 'repositories_groups':
151 151 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
152 152 %else:
153 153 --
154 154 %endif
155 155 </td>
156 156 </tr>
157 157 %endfor
158 158 </tbody>
159 159 </table>
160 160 </div>
161 161 %endif
162 162 %endfor
163 163 </div>
164 164 </div>
165 165 <div class="box box-left" style="clear:left">
166 166 <!-- box / title -->
167 167 <div class="title">
168 168 <h5>${_('Allowed IP addresses')}</h5>
169 169 </div>
170 170
171 171 <div class="ips_wrap">
172 172 <table class="noborder">
173 173 %if c.user_ip_map:
174 174 %for ip in c.user_ip_map:
175 175 <tr>
176 176 <td><div class="ip">${ip.ip_addr}</div></td>
177 177 <td><div class="ip">${h.ip_range(ip.ip_addr)}</div></td>
178 178 <td>
179 179 ${h.form(url('user_ips_delete', id=c.user.user_id),method='delete')}
180 180 ${h.hidden('del_ip',ip.ip_id)}
181 181 ${h.hidden('default_user', 'True')}
182 182 ${h.submit('remove_',_('delete'),id="remove_ip_%s" % ip.ip_id,
183 183 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this ip: %s') % ip.ip_addr+"');")}
184 184 ${h.end_form()}
185 185 </td>
186 186 </tr>
187 187 %endfor
188 188 %else:
189 189 <tr><td><div class="ip">${_('All IP addresses are allowed')}</div></td></tr>
190 190 %endif
191 191 </table>
192 192 </div>
193 193
194 194 ${h.form(url('user_ips', id=c.user.user_id),method='put')}
195 195 <div class="form">
196 196 <!-- fields -->
197 197 <div class="fields">
198 198 <div class="field">
199 199 <div class="label">
200 200 <label for="new_ip">${_('New ip address')}:</label>
201 201 </div>
202 202 <div class="input">
203 203 ${h.hidden('default_user', 'True')}
204 204 ${h.text('new_ip', class_='medium')}
205 205 </div>
206 206 </div>
207 207 <div class="buttons">
208 208 ${h.submit('save',_('Add'),class_="ui-btn large")}
209 209 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
210 210 </div>
211 211 </div>
212 212 </div>
213 213 ${h.end_form()}
214 214 </div>
215 215 </%def>
@@ -1,228 +1,228 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Edit user group')} ${c.users_group.users_group_name} &middot; ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 10 &raquo;
11 11 ${h.link_to(_('UserGroups'),h.url('users_groups'))}
12 12 &raquo;
13 13 ${_('edit')} "${c.users_group.users_group_name}"
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box box-left">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <!-- end box / title -->
27 27 ${h.form(url('users_group', id=c.users_group.users_group_id),method='put', id='edit_users_group')}
28 28 <div class="form">
29 29 <!-- fields -->
30 30 <div class="fields">
31 31 <div class="field">
32 32 <div class="label">
33 33 <label for="users_group_name">${_('Group name')}:</label>
34 34 </div>
35 35 <div class="input">
36 36 ${h.text('users_group_name',class_='small')}
37 37 </div>
38 38 </div>
39 39
40 40 <div class="field">
41 41 <div class="label label-checkbox">
42 42 <label for="users_group_active">${_('Active')}:</label>
43 43 </div>
44 44 <div class="checkboxes">
45 45 ${h.checkbox('users_group_active',value=True)}
46 46 </div>
47 47 </div>
48 48 <div class="field">
49 49 <div class="label">
50 50 <label for="users_group_active">${_('Members')}:</label>
51 51 </div>
52 52 <div class="select">
53 53 <table>
54 54 <tr>
55 55 <td>
56 56 <div>
57 57 <div style="float:left">
58 <div class="text" style="padding: 0px 0px 6px;">${_('Choosen group members')}</div>
58 <div class="text" style="padding: 0px 0px 6px;">${_('Chosen group members')}</div>
59 59 ${h.select('users_group_members',[x[0] for x in c.group_members],c.group_members,multiple=True,size=8,style="min-width:210px")}
60 60 <div id="remove_all_elements" style="cursor:pointer;text-align:center">
61 61 ${_('Remove all elements')}
62 62 <img alt="remove" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_right.png')}"/>
63 63 </div>
64 64 </div>
65 65 <div style="float:left;width:20px;padding-top:50px">
66 66 <img alt="add" id="add_element"
67 67 style="padding:2px;cursor:pointer"
68 68 src="${h.url('/images/icons/arrow_left.png')}"/>
69 69 <br />
70 70 <img alt="remove" id="remove_element"
71 71 style="padding:2px;cursor:pointer"
72 72 src="${h.url('/images/icons/arrow_right.png')}"/>
73 73 </div>
74 74 <div style="float:left">
75 75 <div class="text" style="padding: 0px 0px 6px;">${_('Available members')}</div>
76 76 ${h.select('available_members',[],c.available_members,multiple=True,size=8,style="min-width:210px")}
77 77 <div id="add_all_elements" style="cursor:pointer;text-align:center">
78 78 <img alt="add" style="vertical-align:text-bottom" src="${h.url('/images/icons/arrow_left.png')}"/>
79 79 ${_('Add all elements')}
80 80 </div>
81 81 </div>
82 82 </div>
83 83 </td>
84 84 </tr>
85 85 </table>
86 86 </div>
87 87
88 88 </div>
89 89 <div class="buttons">
90 90 ${h.submit('save',_('save'),class_="ui-btn large")}
91 91 </div>
92 92 </div>
93 93 </div>
94 94 ${h.end_form()}
95 95 </div>
96 96
97 97 <div class="box box-right">
98 98 <!-- box / title -->
99 99 <div class="title">
100 100 <h5>${_('Permissions')}</h5>
101 101 </div>
102 102 ${h.form(url('users_group_perm', id=c.users_group.users_group_id), method='put')}
103 103 <div class="form">
104 104 <!-- fields -->
105 105 <div class="fields">
106 106 <div class="field">
107 107 <div class="label label-checkbox">
108 108 <label for="inherit_permissions">${_('Inherit default permissions')}:</label>
109 109 </div>
110 110 <div class="checkboxes">
111 111 ${h.checkbox('inherit_default_permissions',value=True)}
112 112 </div>
113 113 <span class="help-block">${h.literal(_('Select to inherit permissions from %s settings. '
114 114 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))}</span>
115 115 </div>
116 116 <div id="inherit_overlay" style="${'opacity:0.3' if c.users_group.inherit_default_permissions else ''}" >
117 117 <div class="field">
118 118 <div class="label label-checkbox">
119 119 <label for="create_repo_perm">${_('Create repositories')}:</label>
120 120 </div>
121 121 <div class="checkboxes">
122 122 ${h.checkbox('create_repo_perm',value=True)}
123 123 </div>
124 124 </div>
125 125 <div class="field">
126 126 <div class="label label-checkbox">
127 127 <label for="fork_repo_perm">${_('Fork repositories')}:</label>
128 128 </div>
129 129 <div class="checkboxes">
130 130 ${h.checkbox('fork_repo_perm',value=True)}
131 131 </div>
132 132 </div>
133 133 </div>
134 134 <div class="buttons">
135 135 ${h.submit('save',_('Save'),class_="ui-btn large")}
136 136 ${h.reset('reset',_('Reset'),class_="ui-btn large")}
137 137 </div>
138 138 </div>
139 139 </div>
140 140 ${h.end_form()}
141 141 </div>
142 142
143 143 <div class="box box-right">
144 144 <!-- box / title -->
145 145 <div class="title">
146 146 <h5>${_('Group members')}</h5>
147 147 </div>
148 148
149 149 <div class="group_members_wrap">
150 150 % if c.group_members_obj:
151 151 <ul class="group_members">
152 152 %for user in c.group_members_obj:
153 153 <li>
154 154 <div class="group_member">
155 155 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,24)}"/> </div>
156 156 <div>${h.link_to(user.username, h.url('edit_user',id=user.user_id))}</div>
157 157 <div>${user.full_name}</div>
158 158 </div>
159 159 </li>
160 160 %endfor
161 161 </ul>
162 162 %else:
163 163 <span class="empty_data">${_('No members yet')}</span>
164 164 %endif
165 165 </div>
166 166 </div>
167 167
168 168 <div class="box box-left">
169 169 <!-- box / title -->
170 170 <div class="title">
171 171 <h5>${_('Permissions defined for this group')}</h5>
172 172 </div>
173 173 ## permissions overview
174 174 <div id="perms" class="table">
175 175 %for section in sorted(c.users_group.permissions.keys()):
176 176 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
177 177 %if not c.users_group.permissions:
178 178 <span class="empty_data">${_('No permissions set yet')}</span>
179 179 %else:
180 180 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
181 181 <table id="tbl_list_repository">
182 182 <thead>
183 183 <tr>
184 184 <th class="left">${_('Name')}</th>
185 185 <th class="left">${_('Permission')}</th>
186 186 <th class="left">${_('Edit Permission')}</th>
187 187 </thead>
188 188 <tbody>
189 189 %for k in c.users_group.permissions[section]:
190 190 <%
191 191 section_perm = c.users_group.permissions[section].get(k)
192 192 _perm = section_perm.split('.')[-1]
193 193 %>
194 194 <tr>
195 195 <td>
196 196 %if section == 'repositories':
197 197 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
198 198 %elif section == 'repositories_groups':
199 199 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
200 200 %endif
201 201 </td>
202 202 <td>
203 203 <span class="perm_tag ${_perm}">${section_perm}</span>
204 204 </td>
205 205 <td>
206 206 %if section == 'repositories':
207 207 <a href="${h.url('edit_repo',repo_name=k,anchor='permissions_manage')}">${_('edit')}</a>
208 208 %elif section == 'repositories_groups':
209 209 <a href="${h.url('edit_repos_group',group_name=k,anchor='permissions_manage')}">${_('edit')}</a>
210 210 %else:
211 211 --
212 212 %endif
213 213 </td>
214 214 </tr>
215 215 %endfor
216 216 </tbody>
217 217 </table>
218 218 </div>
219 219 %endif
220 220 %endfor
221 221 </div>
222 222 </div>
223 223
224 224
225 225 <script type="text/javascript">
226 226 MultiSelectWidget('users_group_members','available_members','edit_users_group');
227 227 </script>
228 228 </%def>
General Comments 0
You need to be logged in to leave comments. Login now