##// END OF EJS Templates
labs: enable by default. We want people to encourage using those
marcink -
r366:3565e711 default
parent child Browse files
Show More
@@ -1,881 +1,881 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2013-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Repositories controller for RhodeCode
24 24 """
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 import formencode
30 30 from formencode import htmlfill
31 31 from pylons import request, tmpl_context as c, url
32 32 from pylons.controllers.util import redirect
33 33 from pylons.i18n.translation import _
34 34 from webob.exc import HTTPForbidden, HTTPNotFound, HTTPBadRequest
35 35
36 36 import rhodecode
37 37 from rhodecode.lib import auth, helpers as h
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasPermissionAllDecorator,
40 40 HasRepoPermissionAllDecorator, NotAnonymous, HasPermissionAny,
41 41 HasRepoGroupPermissionAny, HasRepoPermissionAnyDecorator)
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.exceptions import AttachedForksError
45 45 from rhodecode.lib.utils import action_logger, repo_name_slug, jsonify
46 46 from rhodecode.lib.utils2 import safe_int, str2bool
47 47 from rhodecode.lib.vcs import RepositoryError
48 48 from rhodecode.model.db import (
49 49 User, Repository, UserFollowing, RepoGroup, RepositoryField)
50 50 from rhodecode.model.forms import (
51 51 RepoForm, RepoFieldForm, RepoPermsForm, RepoVcsSettingsForm,
52 52 IssueTrackerPatternsForm)
53 53 from rhodecode.model.meta import Session
54 54 from rhodecode.model.repo import RepoModel
55 55 from rhodecode.model.scm import ScmModel, RepoGroupList, RepoList
56 56 from rhodecode.model.settings import (
57 57 SettingsModel, IssueTrackerSettingsModel, VcsSettingsModel,
58 58 SettingNotFound)
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class ReposController(BaseRepoController):
64 64 """
65 65 REST Controller styled on the Atom Publishing Protocol"""
66 66 # To properly map this controller, ensure your config/routing.py
67 67 # file has a resource setup:
68 68 # map.resource('repo', 'repos')
69 69
70 70 @LoginRequired()
71 71 def __before__(self):
72 72 super(ReposController, self).__before__()
73 73
74 74 def _load_repo(self, repo_name):
75 75 repo_obj = Repository.get_by_repo_name(repo_name)
76 76
77 77 if repo_obj is None:
78 78 h.not_mapped_error(repo_name)
79 79 return redirect(url('repos'))
80 80
81 81 return repo_obj
82 82
83 83 def __load_defaults(self, repo=None):
84 84 acl_groups = RepoGroupList(RepoGroup.query().all(),
85 85 perm_set=['group.write', 'group.admin'])
86 86 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
87 87 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
88 88
89 89 # in case someone no longer have a group.write access to a repository
90 90 # pre fill the list with this entry, we don't care if this is the same
91 91 # but it will allow saving repo data properly.
92 92
93 93 repo_group = None
94 94 if repo:
95 95 repo_group = repo.group
96 96 if repo_group and unicode(repo_group.group_id) not in c.repo_groups_choices:
97 97 c.repo_groups_choices.append(unicode(repo_group.group_id))
98 98 c.repo_groups.append(RepoGroup._generate_choice(repo_group))
99 99
100 100 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
101 101 c.landing_revs_choices = choices
102 102
103 103 def __load_data(self, repo_name=None):
104 104 """
105 105 Load defaults settings for edit, and update
106 106
107 107 :param repo_name:
108 108 """
109 109 c.repo_info = self._load_repo(repo_name)
110 110 self.__load_defaults(c.repo_info)
111 111
112 112 # override defaults for exact repo info here git/hg etc
113 113 if not c.repository_requirements_missing:
114 114 choices, c.landing_revs = ScmModel().get_repo_landing_revs(
115 115 c.repo_info)
116 116 c.landing_revs_choices = choices
117 117 defaults = RepoModel()._get_defaults(repo_name)
118 118
119 119 return defaults
120 120
121 121 def _log_creation_exception(self, e, repo_name):
122 122 reason = None
123 123 if len(e.args) == 2:
124 124 reason = e.args[1]
125 125
126 126 if reason == 'INVALID_CERTIFICATE':
127 127 log.exception(
128 128 'Exception creating a repository: invalid certificate')
129 129 msg = (_('Error creating repository %s: invalid certificate')
130 130 % repo_name)
131 131 else:
132 132 log.exception("Exception creating a repository")
133 133 msg = (_('Error creating repository %s')
134 134 % repo_name)
135 135
136 136 return msg
137 137
138 138 @NotAnonymous()
139 139 def index(self, format='html'):
140 140 """GET /repos: All items in the collection"""
141 141 # url('repos')
142 142
143 143 repo_list = Repository.get_all_repos()
144 144 c.repo_list = RepoList(repo_list, perm_set=['repository.admin'])
145 145 repos_data = RepoModel().get_repos_as_dict(
146 146 repo_list=c.repo_list, admin=True, super_user_actions=True)
147 147 # json used to render the grid
148 148 c.data = json.dumps(repos_data)
149 149
150 150 return render('admin/repos/repos.html')
151 151
152 152 # perms check inside
153 153 @NotAnonymous()
154 154 @auth.CSRFRequired()
155 155 def create(self):
156 156 """
157 157 POST /repos: Create a new item"""
158 158 # url('repos')
159 159
160 160 self.__load_defaults()
161 161 form_result = {}
162 162 task_id = None
163 163 try:
164 164 # CanWriteToGroup validators checks permissions of this POST
165 165 form_result = RepoForm(repo_groups=c.repo_groups_choices,
166 166 landing_revs=c.landing_revs_choices)()\
167 167 .to_python(dict(request.POST))
168 168
169 169 # create is done sometimes async on celery, db transaction
170 170 # management is handled there.
171 171 task = RepoModel().create(form_result, c.rhodecode_user.user_id)
172 172 from celery.result import BaseAsyncResult
173 173 if isinstance(task, BaseAsyncResult):
174 174 task_id = task.task_id
175 175 except formencode.Invalid as errors:
176 176 c.personal_repo_group = RepoGroup.get_by_group_name(
177 177 c.rhodecode_user.username)
178 178 return htmlfill.render(
179 179 render('admin/repos/repo_add.html'),
180 180 defaults=errors.value,
181 181 errors=errors.error_dict or {},
182 182 prefix_error=False,
183 183 encoding="UTF-8",
184 184 force_defaults=False)
185 185
186 186 except Exception as e:
187 187 msg = self._log_creation_exception(e, form_result.get('repo_name'))
188 188 h.flash(msg, category='error')
189 189 return redirect(url('home'))
190 190
191 191 return redirect(h.url('repo_creating_home',
192 192 repo_name=form_result['repo_name_full'],
193 193 task_id=task_id))
194 194
195 195 # perms check inside
196 196 @NotAnonymous()
197 197 def create_repository(self):
198 198 """GET /_admin/create_repository: Form to create a new item"""
199 199 new_repo = request.GET.get('repo', '')
200 200 parent_group = request.GET.get('parent_group')
201 201 if not HasPermissionAny('hg.admin', 'hg.create.repository')():
202 202 # you're not super admin nor have global create permissions,
203 203 # but maybe you have at least write permission to a parent group ?
204 204 _gr = RepoGroup.get(parent_group)
205 205 gr_name = _gr.group_name if _gr else None
206 206 # create repositories with write permission on group is set to true
207 207 create_on_write = HasPermissionAny('hg.create.write_on_repogroup.true')()
208 208 group_admin = HasRepoGroupPermissionAny('group.admin')(group_name=gr_name)
209 209 group_write = HasRepoGroupPermissionAny('group.write')(group_name=gr_name)
210 210 if not (group_admin or (group_write and create_on_write)):
211 211 raise HTTPForbidden
212 212
213 213 acl_groups = RepoGroupList(RepoGroup.query().all(),
214 214 perm_set=['group.write', 'group.admin'])
215 215 c.repo_groups = RepoGroup.groups_choices(groups=acl_groups)
216 216 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
217 217 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
218 218 c.personal_repo_group = RepoGroup.get_by_group_name(c.rhodecode_user.username)
219 219 c.new_repo = repo_name_slug(new_repo)
220 220
221 221 ## apply the defaults from defaults page
222 222 defaults = SettingsModel().get_default_repo_settings(strip_prefix=True)
223 223 # set checkbox to autochecked
224 224 defaults['repo_copy_permissions'] = True
225 225 if parent_group:
226 226 defaults.update({'repo_group': parent_group})
227 227
228 228 return htmlfill.render(
229 229 render('admin/repos/repo_add.html'),
230 230 defaults=defaults,
231 231 errors={},
232 232 prefix_error=False,
233 233 encoding="UTF-8",
234 234 force_defaults=False
235 235 )
236 236
237 237 @NotAnonymous()
238 238 def repo_creating(self, repo_name):
239 239 c.repo = repo_name
240 240 c.task_id = request.GET.get('task_id')
241 241 if not c.repo:
242 242 raise HTTPNotFound()
243 243 return render('admin/repos/repo_creating.html')
244 244
245 245 @NotAnonymous()
246 246 @jsonify
247 247 def repo_check(self, repo_name):
248 248 c.repo = repo_name
249 249 task_id = request.GET.get('task_id')
250 250
251 251 if task_id and task_id not in ['None']:
252 252 import rhodecode
253 253 from celery.result import AsyncResult
254 254 if rhodecode.CELERY_ENABLED:
255 255 task = AsyncResult(task_id)
256 256 if task.failed():
257 257 msg = self._log_creation_exception(task.result, c.repo)
258 258 h.flash(msg, category='error')
259 259 return redirect(url('home'), code=501)
260 260
261 261 repo = Repository.get_by_repo_name(repo_name)
262 262 if repo and repo.repo_state == Repository.STATE_CREATED:
263 263 if repo.clone_uri:
264 264 clone_uri = repo.clone_uri_hidden
265 265 h.flash(_('Created repository %s from %s')
266 266 % (repo.repo_name, clone_uri), category='success')
267 267 else:
268 268 repo_url = h.link_to(repo.repo_name,
269 269 h.url('summary_home',
270 270 repo_name=repo.repo_name))
271 271 fork = repo.fork
272 272 if fork:
273 273 fork_name = fork.repo_name
274 274 h.flash(h.literal(_('Forked repository %s as %s')
275 275 % (fork_name, repo_url)), category='success')
276 276 else:
277 277 h.flash(h.literal(_('Created repository %s') % repo_url),
278 278 category='success')
279 279 return {'result': True}
280 280 return {'result': False}
281 281
282 282 @HasRepoPermissionAllDecorator('repository.admin')
283 283 @auth.CSRFRequired()
284 284 def update(self, repo_name):
285 285 """
286 286 PUT /repos/repo_name: Update an existing item"""
287 287 # Forms posted to this method should contain a hidden field:
288 288 # <input type="hidden" name="_method" value="PUT" />
289 289 # Or using helpers:
290 290 # h.form(url('repo', repo_name=ID),
291 291 # method='put')
292 292 # url('repo', repo_name=ID)
293 293
294 294 self.__load_data(repo_name)
295 295 c.active = 'settings'
296 296 c.repo_fields = RepositoryField.query()\
297 297 .filter(RepositoryField.repository == c.repo_info).all()
298 298
299 299 repo_model = RepoModel()
300 300 changed_name = repo_name
301 301
302 302 # override the choices with extracted revisions !
303 303 c.personal_repo_group = RepoGroup.get_by_group_name(
304 304 c.rhodecode_user.username)
305 305 repo = Repository.get_by_repo_name(repo_name)
306 306 old_data = {
307 307 'repo_name': repo_name,
308 308 'repo_group': repo.group.get_dict() if repo.group else {},
309 309 'repo_type': repo.repo_type,
310 310 }
311 311 _form = RepoForm(
312 312 edit=True, old_data=old_data, repo_groups=c.repo_groups_choices,
313 313 landing_revs=c.landing_revs_choices, allow_disabled=True)()
314 314
315 315 try:
316 316 form_result = _form.to_python(dict(request.POST))
317 317 repo = repo_model.update(repo_name, **form_result)
318 318 ScmModel().mark_for_invalidation(repo_name)
319 319 h.flash(_('Repository %s updated successfully') % repo_name,
320 320 category='success')
321 321 changed_name = repo.repo_name
322 322 action_logger(c.rhodecode_user, 'admin_updated_repo',
323 323 changed_name, self.ip_addr, self.sa)
324 324 Session().commit()
325 325 except formencode.Invalid as errors:
326 326 defaults = self.__load_data(repo_name)
327 327 defaults.update(errors.value)
328 328 return htmlfill.render(
329 329 render('admin/repos/repo_edit.html'),
330 330 defaults=defaults,
331 331 errors=errors.error_dict or {},
332 332 prefix_error=False,
333 333 encoding="UTF-8",
334 334 force_defaults=False)
335 335
336 336 except Exception:
337 337 log.exception("Exception during update of repository")
338 338 h.flash(_('Error occurred during update of repository %s') \
339 339 % repo_name, category='error')
340 340 return redirect(url('edit_repo', repo_name=changed_name))
341 341
342 342 @HasRepoPermissionAllDecorator('repository.admin')
343 343 @auth.CSRFRequired()
344 344 def delete(self, repo_name):
345 345 """
346 346 DELETE /repos/repo_name: Delete an existing item"""
347 347 # Forms posted to this method should contain a hidden field:
348 348 # <input type="hidden" name="_method" value="DELETE" />
349 349 # Or using helpers:
350 350 # h.form(url('repo', repo_name=ID),
351 351 # method='delete')
352 352 # url('repo', repo_name=ID)
353 353
354 354 repo_model = RepoModel()
355 355 repo = repo_model.get_by_repo_name(repo_name)
356 356 if not repo:
357 357 h.not_mapped_error(repo_name)
358 358 return redirect(url('repos'))
359 359 try:
360 360 _forks = repo.forks.count()
361 361 handle_forks = None
362 362 if _forks and request.POST.get('forks'):
363 363 do = request.POST['forks']
364 364 if do == 'detach_forks':
365 365 handle_forks = 'detach'
366 366 h.flash(_('Detached %s forks') % _forks, category='success')
367 367 elif do == 'delete_forks':
368 368 handle_forks = 'delete'
369 369 h.flash(_('Deleted %s forks') % _forks, category='success')
370 370 repo_model.delete(repo, forks=handle_forks)
371 371 action_logger(c.rhodecode_user, 'admin_deleted_repo',
372 372 repo_name, self.ip_addr, self.sa)
373 373 ScmModel().mark_for_invalidation(repo_name)
374 374 h.flash(_('Deleted repository %s') % repo_name, category='success')
375 375 Session().commit()
376 376 except AttachedForksError:
377 377 h.flash(_('Cannot delete %s it still contains attached forks')
378 378 % repo_name, category='warning')
379 379
380 380 except Exception:
381 381 log.exception("Exception during deletion of repository")
382 382 h.flash(_('An error occurred during deletion of %s') % repo_name,
383 383 category='error')
384 384
385 385 return redirect(url('repos'))
386 386
387 387 @HasPermissionAllDecorator('hg.admin')
388 388 def show(self, repo_name, format='html'):
389 389 """GET /repos/repo_name: Show a specific item"""
390 390 # url('repo', repo_name=ID)
391 391
392 392 @HasRepoPermissionAllDecorator('repository.admin')
393 393 def edit(self, repo_name):
394 394 """GET /repo_name/settings: Form to edit an existing item"""
395 395 # url('edit_repo', repo_name=ID)
396 396 defaults = self.__load_data(repo_name)
397 397 if 'clone_uri' in defaults:
398 398 del defaults['clone_uri']
399 399
400 400 c.repo_fields = RepositoryField.query()\
401 401 .filter(RepositoryField.repository == c.repo_info).all()
402 402 c.personal_repo_group = RepoGroup.get_by_group_name(
403 403 c.rhodecode_user.username)
404 404 c.active = 'settings'
405 405 return htmlfill.render(
406 406 render('admin/repos/repo_edit.html'),
407 407 defaults=defaults,
408 408 encoding="UTF-8",
409 409 force_defaults=False)
410 410
411 411 @HasRepoPermissionAllDecorator('repository.admin')
412 412 def edit_permissions(self, repo_name):
413 413 """GET /repo_name/settings: Form to edit an existing item"""
414 414 # url('edit_repo', repo_name=ID)
415 415 c.repo_info = self._load_repo(repo_name)
416 416 c.active = 'permissions'
417 417 defaults = RepoModel()._get_defaults(repo_name)
418 418
419 419 return htmlfill.render(
420 420 render('admin/repos/repo_edit.html'),
421 421 defaults=defaults,
422 422 encoding="UTF-8",
423 423 force_defaults=False)
424 424
425 425 @HasRepoPermissionAllDecorator('repository.admin')
426 426 @auth.CSRFRequired()
427 427 def edit_permissions_update(self, repo_name):
428 428 form = RepoPermsForm()().to_python(request.POST)
429 429 RepoModel().update_permissions(repo_name,
430 430 form['perm_additions'], form['perm_updates'], form['perm_deletions'])
431 431
432 432 #TODO: implement this
433 433 #action_logger(c.rhodecode_user, 'admin_changed_repo_permissions',
434 434 # repo_name, self.ip_addr, self.sa)
435 435 Session().commit()
436 436 h.flash(_('Repository permissions updated'), category='success')
437 437 return redirect(url('edit_repo_perms', repo_name=repo_name))
438 438
439 439 @HasRepoPermissionAllDecorator('repository.admin')
440 440 def edit_fields(self, repo_name):
441 441 """GET /repo_name/settings: Form to edit an existing item"""
442 442 # url('edit_repo', repo_name=ID)
443 443 c.repo_info = self._load_repo(repo_name)
444 444 c.repo_fields = RepositoryField.query()\
445 445 .filter(RepositoryField.repository == c.repo_info).all()
446 446 c.active = 'fields'
447 447 if request.POST:
448 448
449 449 return redirect(url('repo_edit_fields'))
450 450 return render('admin/repos/repo_edit.html')
451 451
452 452 @HasRepoPermissionAllDecorator('repository.admin')
453 453 @auth.CSRFRequired()
454 454 def create_repo_field(self, repo_name):
455 455 try:
456 456 form_result = RepoFieldForm()().to_python(dict(request.POST))
457 457 RepoModel().add_repo_field(
458 458 repo_name, form_result['new_field_key'],
459 459 field_type=form_result['new_field_type'],
460 460 field_value=form_result['new_field_value'],
461 461 field_label=form_result['new_field_label'],
462 462 field_desc=form_result['new_field_desc'])
463 463
464 464 Session().commit()
465 465 except Exception as e:
466 466 log.exception("Exception creating field")
467 467 msg = _('An error occurred during creation of field')
468 468 if isinstance(e, formencode.Invalid):
469 469 msg += ". " + e.msg
470 470 h.flash(msg, category='error')
471 471 return redirect(url('edit_repo_fields', repo_name=repo_name))
472 472
473 473 @HasRepoPermissionAllDecorator('repository.admin')
474 474 @auth.CSRFRequired()
475 475 def delete_repo_field(self, repo_name, field_id):
476 476 field = RepositoryField.get_or_404(field_id)
477 477 try:
478 478 RepoModel().delete_repo_field(repo_name, field.field_key)
479 479 Session().commit()
480 480 except Exception as e:
481 481 log.exception("Exception during removal of field")
482 482 msg = _('An error occurred during removal of field')
483 483 h.flash(msg, category='error')
484 484 return redirect(url('edit_repo_fields', repo_name=repo_name))
485 485
486 486 @HasRepoPermissionAllDecorator('repository.admin')
487 487 def edit_advanced(self, repo_name):
488 488 """GET /repo_name/settings: Form to edit an existing item"""
489 489 # url('edit_repo', repo_name=ID)
490 490 c.repo_info = self._load_repo(repo_name)
491 491 c.default_user_id = User.get_default_user().user_id
492 492 c.in_public_journal = UserFollowing.query()\
493 493 .filter(UserFollowing.user_id == c.default_user_id)\
494 494 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
495 495
496 496 c.active = 'advanced'
497 497 c.has_origin_repo_read_perm = False
498 498 if c.repo_info.fork:
499 499 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
500 500 'repository.write', 'repository.read', 'repository.admin')(
501 501 c.repo_info.fork.repo_name, 'repo set as fork page')
502 502
503 503 if request.POST:
504 504 return redirect(url('repo_edit_advanced'))
505 505 return render('admin/repos/repo_edit.html')
506 506
507 507 @HasRepoPermissionAllDecorator('repository.admin')
508 508 @auth.CSRFRequired()
509 509 def edit_advanced_journal(self, repo_name):
510 510 """
511 511 Set's this repository to be visible in public journal,
512 512 in other words assing default user to follow this repo
513 513
514 514 :param repo_name:
515 515 """
516 516
517 517 try:
518 518 repo_id = Repository.get_by_repo_name(repo_name).repo_id
519 519 user_id = User.get_default_user().user_id
520 520 self.scm_model.toggle_following_repo(repo_id, user_id)
521 521 h.flash(_('Updated repository visibility in public journal'),
522 522 category='success')
523 523 Session().commit()
524 524 except Exception:
525 525 h.flash(_('An error occurred during setting this'
526 526 ' repository in public journal'),
527 527 category='error')
528 528
529 529 return redirect(url('edit_repo_advanced', repo_name=repo_name))
530 530
531 531 @HasRepoPermissionAllDecorator('repository.admin')
532 532 @auth.CSRFRequired()
533 533 def edit_advanced_fork(self, repo_name):
534 534 """
535 535 Mark given repository as a fork of another
536 536
537 537 :param repo_name:
538 538 """
539 539
540 540 new_fork_id = request.POST.get('id_fork_of')
541 541 try:
542 542
543 543 if new_fork_id and not new_fork_id.isdigit():
544 544 log.error('Given fork id %s is not an INT', new_fork_id)
545 545
546 546 fork_id = safe_int(new_fork_id)
547 547 repo = ScmModel().mark_as_fork(repo_name, fork_id,
548 548 c.rhodecode_user.username)
549 549 fork = repo.fork.repo_name if repo.fork else _('Nothing')
550 550 Session().commit()
551 551 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
552 552 category='success')
553 553 except RepositoryError as e:
554 554 log.exception("Repository Error occurred")
555 555 h.flash(str(e), category='error')
556 556 except Exception as e:
557 557 log.exception("Exception while editing fork")
558 558 h.flash(_('An error occurred during this operation'),
559 559 category='error')
560 560
561 561 return redirect(url('edit_repo_advanced', repo_name=repo_name))
562 562
563 563 @HasRepoPermissionAllDecorator('repository.admin')
564 564 @auth.CSRFRequired()
565 565 def edit_advanced_locking(self, repo_name):
566 566 """
567 567 Unlock repository when it is locked !
568 568
569 569 :param repo_name:
570 570 """
571 571 try:
572 572 repo = Repository.get_by_repo_name(repo_name)
573 573 if request.POST.get('set_lock'):
574 574 Repository.lock(repo, c.rhodecode_user.user_id,
575 575 lock_reason=Repository.LOCK_WEB)
576 576 h.flash(_('Locked repository'), category='success')
577 577 elif request.POST.get('set_unlock'):
578 578 Repository.unlock(repo)
579 579 h.flash(_('Unlocked repository'), category='success')
580 580 except Exception as e:
581 581 log.exception("Exception during unlocking")
582 582 h.flash(_('An error occurred during unlocking'),
583 583 category='error')
584 584 return redirect(url('edit_repo_advanced', repo_name=repo_name))
585 585
586 586 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
587 587 @auth.CSRFRequired()
588 588 def toggle_locking(self, repo_name):
589 589 """
590 590 Toggle locking of repository by simple GET call to url
591 591
592 592 :param repo_name:
593 593 """
594 594
595 595 try:
596 596 repo = Repository.get_by_repo_name(repo_name)
597 597
598 598 if repo.enable_locking:
599 599 if repo.locked[0]:
600 600 Repository.unlock(repo)
601 601 action = _('Unlocked')
602 602 else:
603 603 Repository.lock(repo, c.rhodecode_user.user_id,
604 604 lock_reason=Repository.LOCK_WEB)
605 605 action = _('Locked')
606 606
607 607 h.flash(_('Repository has been %s') % action,
608 608 category='success')
609 609 except Exception:
610 610 log.exception("Exception during unlocking")
611 611 h.flash(_('An error occurred during unlocking'),
612 612 category='error')
613 613 return redirect(url('summary_home', repo_name=repo_name))
614 614
615 615 @HasRepoPermissionAllDecorator('repository.admin')
616 616 @auth.CSRFRequired()
617 617 def edit_caches(self, repo_name):
618 618 """PUT /{repo_name}/settings/caches: invalidate the repo caches."""
619 619 try:
620 620 ScmModel().mark_for_invalidation(repo_name, delete=True)
621 621 Session().commit()
622 622 h.flash(_('Cache invalidation successful'),
623 623 category='success')
624 624 except Exception:
625 625 log.exception("Exception during cache invalidation")
626 626 h.flash(_('An error occurred during cache invalidation'),
627 627 category='error')
628 628
629 629 return redirect(url('edit_repo_caches', repo_name=c.repo_name))
630 630
631 631 @HasRepoPermissionAllDecorator('repository.admin')
632 632 def edit_caches_form(self, repo_name):
633 633 """GET /repo_name/settings: Form to edit an existing item"""
634 634 # url('edit_repo', repo_name=ID)
635 635 c.repo_info = self._load_repo(repo_name)
636 636 c.active = 'caches'
637 637
638 638 return render('admin/repos/repo_edit.html')
639 639
640 640 @HasRepoPermissionAllDecorator('repository.admin')
641 641 @auth.CSRFRequired()
642 642 def edit_remote(self, repo_name):
643 643 """PUT /{repo_name}/settings/remote: edit the repo remote."""
644 644 try:
645 645 ScmModel().pull_changes(repo_name, c.rhodecode_user.username)
646 646 h.flash(_('Pulled from remote location'), category='success')
647 647 except Exception:
648 648 log.exception("Exception during pull from remote")
649 649 h.flash(_('An error occurred during pull from remote location'),
650 650 category='error')
651 651 return redirect(url('edit_repo_remote', repo_name=c.repo_name))
652 652
653 653 @HasRepoPermissionAllDecorator('repository.admin')
654 654 def edit_remote_form(self, repo_name):
655 655 """GET /repo_name/settings: Form to edit an existing item"""
656 656 # url('edit_repo', repo_name=ID)
657 657 c.repo_info = self._load_repo(repo_name)
658 658 c.active = 'remote'
659 659
660 660 return render('admin/repos/repo_edit.html')
661 661
662 662 @HasRepoPermissionAllDecorator('repository.admin')
663 663 @auth.CSRFRequired()
664 664 def edit_statistics(self, repo_name):
665 665 """PUT /{repo_name}/settings/statistics: reset the repo statistics."""
666 666 try:
667 667 RepoModel().delete_stats(repo_name)
668 668 Session().commit()
669 669 except Exception as e:
670 670 log.error(traceback.format_exc())
671 671 h.flash(_('An error occurred during deletion of repository stats'),
672 672 category='error')
673 673 return redirect(url('edit_repo_statistics', repo_name=c.repo_name))
674 674
675 675 @HasRepoPermissionAllDecorator('repository.admin')
676 676 def edit_statistics_form(self, repo_name):
677 677 """GET /repo_name/settings: Form to edit an existing item"""
678 678 # url('edit_repo', repo_name=ID)
679 679 c.repo_info = self._load_repo(repo_name)
680 680 repo = c.repo_info.scm_instance()
681 681
682 682 if c.repo_info.stats:
683 683 # this is on what revision we ended up so we add +1 for count
684 684 last_rev = c.repo_info.stats.stat_on_revision + 1
685 685 else:
686 686 last_rev = 0
687 687 c.stats_revision = last_rev
688 688
689 689 c.repo_last_rev = repo.count()
690 690
691 691 if last_rev == 0 or c.repo_last_rev == 0:
692 692 c.stats_percentage = 0
693 693 else:
694 694 c.stats_percentage = '%.2f' % ((float((last_rev)) / c.repo_last_rev) * 100)
695 695
696 696 c.active = 'statistics'
697 697
698 698 return render('admin/repos/repo_edit.html')
699 699
700 700 @HasRepoPermissionAllDecorator('repository.admin')
701 701 @auth.CSRFRequired()
702 702 def repo_issuetracker_test(self, repo_name):
703 703 if request.is_xhr:
704 704 return h.urlify_commit_message(
705 705 request.POST.get('test_text', ''),
706 706 repo_name)
707 707 else:
708 708 raise HTTPBadRequest()
709 709
710 710 @HasRepoPermissionAllDecorator('repository.admin')
711 711 @auth.CSRFRequired()
712 712 def repo_issuetracker_delete(self, repo_name):
713 713 uid = request.POST.get('uid')
714 714 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
715 715 try:
716 716 repo_settings.delete_entries(uid)
717 717 except Exception:
718 718 h.flash(_('Error occurred during deleting issue tracker entry'),
719 719 category='error')
720 720 else:
721 721 h.flash(_('Removed issue tracker entry'), category='success')
722 722 return redirect(url('repo_settings_issuetracker',
723 723 repo_name=repo_name))
724 724
725 725 def _update_patterns(self, form, repo_settings):
726 726 for uid in form['delete_patterns']:
727 727 repo_settings.delete_entries(uid)
728 728
729 729 for pattern in form['patterns']:
730 730 for setting, value, type_ in pattern:
731 731 sett = repo_settings.create_or_update_setting(
732 732 setting, value, type_)
733 733 Session().add(sett)
734 734
735 735 Session().commit()
736 736
737 737 @HasRepoPermissionAllDecorator('repository.admin')
738 738 @auth.CSRFRequired()
739 739 def repo_issuetracker_save(self, repo_name):
740 740 # Save inheritance
741 741 repo_settings = IssueTrackerSettingsModel(repo=repo_name)
742 742 inherited = (request.POST.get('inherit_global_issuetracker')
743 743 == "inherited")
744 744 repo_settings.inherit_global_settings = inherited
745 745 Session().commit()
746 746
747 747 form = IssueTrackerPatternsForm()().to_python(request.POST)
748 748 if form:
749 749 self._update_patterns(form, repo_settings)
750 750
751 751 h.flash(_('Updated issue tracker entries'), category='success')
752 752 return redirect(url('repo_settings_issuetracker',
753 753 repo_name=repo_name))
754 754
755 755 @HasRepoPermissionAllDecorator('repository.admin')
756 756 def repo_issuetracker(self, repo_name):
757 757 """GET /admin/settings/issue-tracker: All items in the collection"""
758 758 c.active = 'issuetracker'
759 759 c.data = 'data'
760 760 c.repo_info = self._load_repo(repo_name)
761 761
762 762 repo = Repository.get_by_repo_name(repo_name)
763 763 c.settings_model = IssueTrackerSettingsModel(repo=repo)
764 764 c.global_patterns = c.settings_model.get_global_settings()
765 765 c.repo_patterns = c.settings_model.get_repo_settings()
766 766
767 767 return render('admin/repos/repo_edit.html')
768 768
769 769 @HasRepoPermissionAllDecorator('repository.admin')
770 770 def repo_settings_vcs(self, repo_name):
771 771 """GET /{repo_name}/settings/vcs/: All items in the collection"""
772 772
773 773 model = VcsSettingsModel(repo=repo_name)
774 774
775 775 c.active = 'vcs'
776 776 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
777 777 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
778 778 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
779 779 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
780 780 c.repo_info = self._load_repo(repo_name)
781 781 defaults = self._vcs_form_defaults(repo_name)
782 782 c.inherit_global_settings = defaults['inherit_global_settings']
783 783 c.labs_active = str2bool(
784 rhodecode.CONFIG.get('labs_settings_active', 'false'))
784 rhodecode.CONFIG.get('labs_settings_active', 'true'))
785 785
786 786 return htmlfill.render(
787 787 render('admin/repos/repo_edit.html'),
788 788 defaults=defaults,
789 789 encoding="UTF-8",
790 790 force_defaults=False)
791 791
792 792 @HasRepoPermissionAllDecorator('repository.admin')
793 793 @auth.CSRFRequired()
794 794 def repo_settings_vcs_update(self, repo_name):
795 795 """POST /{repo_name}/settings/vcs/: All items in the collection"""
796 796 c.active = 'vcs'
797 797
798 798 model = VcsSettingsModel(repo=repo_name)
799 799 c.global_svn_branch_patterns = model.get_global_svn_branch_patterns()
800 800 c.global_svn_tag_patterns = model.get_global_svn_tag_patterns()
801 801 c.svn_branch_patterns = model.get_repo_svn_branch_patterns()
802 802 c.svn_tag_patterns = model.get_repo_svn_tag_patterns()
803 803 c.repo_info = self._load_repo(repo_name)
804 804 defaults = self._vcs_form_defaults(repo_name)
805 805 c.inherit_global_settings = defaults['inherit_global_settings']
806 806
807 807 application_form = RepoVcsSettingsForm(repo_name)()
808 808 try:
809 809 form_result = application_form.to_python(dict(request.POST))
810 810 except formencode.Invalid as errors:
811 811 h.flash(
812 812 _("Some form inputs contain invalid data."),
813 813 category='error')
814 814 return htmlfill.render(
815 815 render('admin/repos/repo_edit.html'),
816 816 defaults=errors.value,
817 817 errors=errors.error_dict or {},
818 818 prefix_error=False,
819 819 encoding="UTF-8",
820 820 force_defaults=False
821 821 )
822 822
823 823 try:
824 824 inherit_global_settings = form_result['inherit_global_settings']
825 825 model.create_or_update_repo_settings(
826 826 form_result, inherit_global_settings=inherit_global_settings)
827 827 except Exception:
828 828 log.exception("Exception while updating settings")
829 829 h.flash(
830 830 _('Error occurred during updating repository VCS settings'),
831 831 category='error')
832 832 else:
833 833 Session().commit()
834 834 h.flash(_('Updated VCS settings'), category='success')
835 835 return redirect(url('repo_vcs_settings', repo_name=repo_name))
836 836
837 837 return htmlfill.render(
838 838 render('admin/repos/repo_edit.html'),
839 839 defaults=self._vcs_form_defaults(repo_name),
840 840 encoding="UTF-8",
841 841 force_defaults=False)
842 842
843 843 @HasRepoPermissionAllDecorator('repository.admin')
844 844 @auth.CSRFRequired()
845 845 @jsonify
846 846 def repo_delete_svn_pattern(self, repo_name):
847 847 if not request.is_xhr:
848 848 return False
849 849
850 850 delete_pattern_id = request.POST.get('delete_svn_pattern')
851 851 model = VcsSettingsModel(repo=repo_name)
852 852 try:
853 853 model.delete_repo_svn_pattern(delete_pattern_id)
854 854 except SettingNotFound:
855 855 raise HTTPBadRequest()
856 856
857 857 Session().commit()
858 858 return True
859 859
860 860 def _vcs_form_defaults(self, repo_name):
861 861 model = VcsSettingsModel(repo=repo_name)
862 862 global_defaults = model.get_global_settings()
863 863
864 864 repo_defaults = {}
865 865 repo_defaults.update(global_defaults)
866 866 repo_defaults.update(model.get_repo_settings())
867 867
868 868 global_defaults = {
869 869 '{}_inherited'.format(k): global_defaults[k]
870 870 for k in global_defaults}
871 871
872 872 defaults = {
873 873 'inherit_global_settings': model.inherit_global_settings
874 874 }
875 875 defaults.update(global_defaults)
876 876 defaults.update(repo_defaults)
877 877 defaults.update({
878 878 'new_svn_branch': '',
879 879 'new_svn_tag': '',
880 880 })
881 881 return defaults
@@ -1,806 +1,806 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 settings controller for rhodecode admin
24 24 """
25 25
26 26 import collections
27 27 import logging
28 28 import urllib2
29 29
30 30 import datetime
31 31 import formencode
32 32 from formencode import htmlfill
33 33 import packaging.version
34 34 from pylons import request, tmpl_context as c, url, config
35 35 from pylons.controllers.util import redirect
36 36 from pylons.i18n.translation import _, lazy_ugettext
37 37 from webob.exc import HTTPBadRequest
38 38
39 39 import rhodecode
40 40 from rhodecode.admin.navigation import navigation_list
41 41 from rhodecode.lib import auth
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
44 44 from rhodecode.lib.base import BaseController, render
45 45 from rhodecode.lib.celerylib import tasks, run_task
46 46 from rhodecode.lib.utils import repo2db_mapper
47 47 from rhodecode.lib.utils2 import (
48 48 str2bool, safe_unicode, AttributeDict, safe_int)
49 49 from rhodecode.lib.compat import OrderedDict
50 50 from rhodecode.lib.ext_json import json
51 51 from rhodecode.lib.utils import jsonify
52 52
53 53 from rhodecode.model.db import RhodeCodeUi, Repository
54 54 from rhodecode.model.forms import ApplicationSettingsForm, \
55 55 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
56 56 LabsSettingsForm, IssueTrackerPatternsForm
57 57
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.notification import EmailNotificationModel
60 60 from rhodecode.model.meta import Session
61 61 from rhodecode.model.settings import (
62 62 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
63 63 SettingsModel)
64 64
65 65 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
66 66
67 67
68 68 log = logging.getLogger(__name__)
69 69
70 70
71 71 class SettingsController(BaseController):
72 72 """REST Controller styled on the Atom Publishing Protocol"""
73 73 # To properly map this controller, ensure your config/routing.py
74 74 # file has a resource setup:
75 75 # map.resource('setting', 'settings', controller='admin/settings',
76 76 # path_prefix='/admin', name_prefix='admin_')
77 77
78 78 @LoginRequired()
79 79 def __before__(self):
80 80 super(SettingsController, self).__before__()
81 81 c.labs_active = str2bool(
82 rhodecode.CONFIG.get('labs_settings_active', 'false'))
82 rhodecode.CONFIG.get('labs_settings_active', 'true'))
83 83 c.navlist = navigation_list(request)
84 84
85 85 def _get_hg_ui_settings(self):
86 86 ret = RhodeCodeUi.query().all()
87 87
88 88 if not ret:
89 89 raise Exception('Could not get application ui settings !')
90 90 settings = {}
91 91 for each in ret:
92 92 k = each.ui_key
93 93 v = each.ui_value
94 94 if k == '/':
95 95 k = 'root_path'
96 96
97 97 if k in ['push_ssl', 'publish']:
98 98 v = str2bool(v)
99 99
100 100 if k.find('.') != -1:
101 101 k = k.replace('.', '_')
102 102
103 103 if each.ui_section in ['hooks', 'extensions']:
104 104 v = each.ui_active
105 105
106 106 settings[each.ui_section + '_' + k] = v
107 107 return settings
108 108
109 109 @HasPermissionAllDecorator('hg.admin')
110 110 @auth.CSRFRequired()
111 111 @jsonify
112 112 def delete_svn_pattern(self):
113 113 if not request.is_xhr:
114 114 raise HTTPBadRequest()
115 115
116 116 delete_pattern_id = request.POST.get('delete_svn_pattern')
117 117 model = VcsSettingsModel()
118 118 try:
119 119 model.delete_global_svn_pattern(delete_pattern_id)
120 120 except SettingNotFound:
121 121 raise HTTPBadRequest()
122 122
123 123 Session().commit()
124 124 return True
125 125
126 126 @HasPermissionAllDecorator('hg.admin')
127 127 @auth.CSRFRequired()
128 128 def settings_vcs_update(self):
129 129 """POST /admin/settings: All items in the collection"""
130 130 # url('admin_settings_vcs')
131 131 c.active = 'vcs'
132 132
133 133 model = VcsSettingsModel()
134 134 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
135 135 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
136 136
137 137 application_form = ApplicationUiSettingsForm()()
138 138 try:
139 139 form_result = application_form.to_python(dict(request.POST))
140 140 except formencode.Invalid as errors:
141 141 h.flash(
142 142 _("Some form inputs contain invalid data."),
143 143 category='error')
144 144 return htmlfill.render(
145 145 render('admin/settings/settings.html'),
146 146 defaults=errors.value,
147 147 errors=errors.error_dict or {},
148 148 prefix_error=False,
149 149 encoding="UTF-8",
150 150 force_defaults=False
151 151 )
152 152
153 153 try:
154 154 model.update_global_ssl_setting(form_result['web_push_ssl'])
155 155 if c.visual.allow_repo_location_change:
156 156 model.update_global_path_setting(
157 157 form_result['paths_root_path'])
158 158 model.update_global_hook_settings(form_result)
159 159 model.create_global_svn_settings(form_result)
160 160 model.create_or_update_global_hg_settings(form_result)
161 161 model.create_or_update_global_pr_settings(form_result)
162 162 except Exception:
163 163 log.exception("Exception while updating settings")
164 164 h.flash(_('Error occurred during updating '
165 165 'application settings'), category='error')
166 166 else:
167 167 Session().commit()
168 168 h.flash(_('Updated VCS settings'), category='success')
169 169 return redirect(url('admin_settings_vcs'))
170 170
171 171 return htmlfill.render(
172 172 render('admin/settings/settings.html'),
173 173 defaults=self._form_defaults(),
174 174 encoding="UTF-8",
175 175 force_defaults=False)
176 176
177 177 @HasPermissionAllDecorator('hg.admin')
178 178 def settings_vcs(self):
179 179 """GET /admin/settings: All items in the collection"""
180 180 # url('admin_settings_vcs')
181 181 c.active = 'vcs'
182 182 model = VcsSettingsModel()
183 183 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
184 184 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
185 185
186 186 return htmlfill.render(
187 187 render('admin/settings/settings.html'),
188 188 defaults=self._form_defaults(),
189 189 encoding="UTF-8",
190 190 force_defaults=False)
191 191
192 192 @HasPermissionAllDecorator('hg.admin')
193 193 @auth.CSRFRequired()
194 194 def settings_mapping_update(self):
195 195 """POST /admin/settings/mapping: All items in the collection"""
196 196 # url('admin_settings_mapping')
197 197 c.active = 'mapping'
198 198 rm_obsolete = request.POST.get('destroy', False)
199 199 invalidate_cache = request.POST.get('invalidate', False)
200 200 log.debug(
201 201 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
202 202
203 203 if invalidate_cache:
204 204 log.debug('invalidating all repositories cache')
205 205 for repo in Repository.get_all():
206 206 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
207 207
208 208 filesystem_repos = ScmModel().repo_scan()
209 209 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
210 210 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
211 211 h.flash(_('Repositories successfully '
212 212 'rescanned added: %s ; removed: %s') %
213 213 (_repr(added), _repr(removed)),
214 214 category='success')
215 215 return redirect(url('admin_settings_mapping'))
216 216
217 217 @HasPermissionAllDecorator('hg.admin')
218 218 def settings_mapping(self):
219 219 """GET /admin/settings/mapping: All items in the collection"""
220 220 # url('admin_settings_mapping')
221 221 c.active = 'mapping'
222 222
223 223 return htmlfill.render(
224 224 render('admin/settings/settings.html'),
225 225 defaults=self._form_defaults(),
226 226 encoding="UTF-8",
227 227 force_defaults=False)
228 228
229 229 @HasPermissionAllDecorator('hg.admin')
230 230 @auth.CSRFRequired()
231 231 def settings_global_update(self):
232 232 """POST /admin/settings/global: All items in the collection"""
233 233 # url('admin_settings_global')
234 234 c.active = 'global'
235 235 application_form = ApplicationSettingsForm()()
236 236 try:
237 237 form_result = application_form.to_python(dict(request.POST))
238 238 except formencode.Invalid as errors:
239 239 return htmlfill.render(
240 240 render('admin/settings/settings.html'),
241 241 defaults=errors.value,
242 242 errors=errors.error_dict or {},
243 243 prefix_error=False,
244 244 encoding="UTF-8",
245 245 force_defaults=False)
246 246
247 247 try:
248 248 settings = [
249 249 ('title', 'rhodecode_title'),
250 250 ('realm', 'rhodecode_realm'),
251 251 ('pre_code', 'rhodecode_pre_code'),
252 252 ('post_code', 'rhodecode_post_code'),
253 253 ('captcha_public_key', 'rhodecode_captcha_public_key'),
254 254 ('captcha_private_key', 'rhodecode_captcha_private_key'),
255 255 ]
256 256 for setting, form_key in settings:
257 257 sett = SettingsModel().create_or_update_setting(
258 258 setting, form_result[form_key])
259 259 Session().add(sett)
260 260
261 261 Session().commit()
262 262 SettingsModel().invalidate_settings_cache()
263 263 h.flash(_('Updated application settings'), category='success')
264 264 except Exception:
265 265 log.exception("Exception while updating application settings")
266 266 h.flash(
267 267 _('Error occurred during updating application settings'),
268 268 category='error')
269 269
270 270 return redirect(url('admin_settings_global'))
271 271
272 272 @HasPermissionAllDecorator('hg.admin')
273 273 def settings_global(self):
274 274 """GET /admin/settings/global: All items in the collection"""
275 275 # url('admin_settings_global')
276 276 c.active = 'global'
277 277
278 278 return htmlfill.render(
279 279 render('admin/settings/settings.html'),
280 280 defaults=self._form_defaults(),
281 281 encoding="UTF-8",
282 282 force_defaults=False)
283 283
284 284 @HasPermissionAllDecorator('hg.admin')
285 285 @auth.CSRFRequired()
286 286 def settings_visual_update(self):
287 287 """POST /admin/settings/visual: All items in the collection"""
288 288 # url('admin_settings_visual')
289 289 c.active = 'visual'
290 290 application_form = ApplicationVisualisationForm()()
291 291 try:
292 292 form_result = application_form.to_python(dict(request.POST))
293 293 except formencode.Invalid as errors:
294 294 return htmlfill.render(
295 295 render('admin/settings/settings.html'),
296 296 defaults=errors.value,
297 297 errors=errors.error_dict or {},
298 298 prefix_error=False,
299 299 encoding="UTF-8",
300 300 force_defaults=False
301 301 )
302 302
303 303 try:
304 304 settings = [
305 305 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
306 306 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
307 307 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
308 308 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
309 309 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
310 310 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
311 311 ('show_version', 'rhodecode_show_version', 'bool'),
312 312 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
313 313 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
314 314 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
315 315 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
316 316 ('support_url', 'rhodecode_support_url', 'unicode'),
317 317 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
318 318 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
319 319 ]
320 320 for setting, form_key, type_ in settings:
321 321 sett = SettingsModel().create_or_update_setting(
322 322 setting, form_result[form_key], type_)
323 323 Session().add(sett)
324 324
325 325 Session().commit()
326 326 SettingsModel().invalidate_settings_cache()
327 327 h.flash(_('Updated visualisation settings'), category='success')
328 328 except Exception:
329 329 log.exception("Exception updating visualization settings")
330 330 h.flash(_('Error occurred during updating '
331 331 'visualisation settings'),
332 332 category='error')
333 333
334 334 return redirect(url('admin_settings_visual'))
335 335
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 def settings_visual(self):
338 338 """GET /admin/settings/visual: All items in the collection"""
339 339 # url('admin_settings_visual')
340 340 c.active = 'visual'
341 341
342 342 return htmlfill.render(
343 343 render('admin/settings/settings.html'),
344 344 defaults=self._form_defaults(),
345 345 encoding="UTF-8",
346 346 force_defaults=False)
347 347
348 348 @HasPermissionAllDecorator('hg.admin')
349 349 @auth.CSRFRequired()
350 350 def settings_issuetracker_test(self):
351 351 if request.is_xhr:
352 352 return h.urlify_commit_message(
353 353 request.POST.get('test_text', ''),
354 354 'repo_group/test_repo1')
355 355 else:
356 356 raise HTTPBadRequest()
357 357
358 358 @HasPermissionAllDecorator('hg.admin')
359 359 @auth.CSRFRequired()
360 360 def settings_issuetracker_delete(self):
361 361 uid = request.POST.get('uid')
362 362 IssueTrackerSettingsModel().delete_entries(uid)
363 363 h.flash(_('Removed issue tracker entry'), category='success')
364 364 return redirect(url('admin_settings_issuetracker'))
365 365
366 366 @HasPermissionAllDecorator('hg.admin')
367 367 def settings_issuetracker(self):
368 368 """GET /admin/settings/issue-tracker: All items in the collection"""
369 369 # url('admin_settings_issuetracker')
370 370 c.active = 'issuetracker'
371 371 defaults = SettingsModel().get_all_settings()
372 372
373 373 entry_key = 'rhodecode_issuetracker_pat_'
374 374
375 375 c.issuetracker_entries = {}
376 376 for k, v in defaults.items():
377 377 if k.startswith(entry_key):
378 378 uid = k[len(entry_key):]
379 379 c.issuetracker_entries[uid] = None
380 380
381 381 for uid in c.issuetracker_entries:
382 382 c.issuetracker_entries[uid] = AttributeDict({
383 383 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
384 384 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
385 385 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
386 386 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
387 387 })
388 388
389 389 return render('admin/settings/settings.html')
390 390
391 391 @HasPermissionAllDecorator('hg.admin')
392 392 @auth.CSRFRequired()
393 393 def settings_issuetracker_save(self):
394 394 settings_model = IssueTrackerSettingsModel()
395 395
396 396 form = IssueTrackerPatternsForm()().to_python(request.POST)
397 397 for uid in form['delete_patterns']:
398 398 settings_model.delete_entries(uid)
399 399
400 400 for pattern in form['patterns']:
401 401 for setting, value, type_ in pattern:
402 402 sett = settings_model.create_or_update_setting(
403 403 setting, value, type_)
404 404 Session().add(sett)
405 405
406 406 Session().commit()
407 407
408 408 SettingsModel().invalidate_settings_cache()
409 409 h.flash(_('Updated issue tracker entries'), category='success')
410 410 return redirect(url('admin_settings_issuetracker'))
411 411
412 412 @HasPermissionAllDecorator('hg.admin')
413 413 @auth.CSRFRequired()
414 414 def settings_email_update(self):
415 415 """POST /admin/settings/email: All items in the collection"""
416 416 # url('admin_settings_email')
417 417 c.active = 'email'
418 418
419 419 test_email = request.POST.get('test_email')
420 420
421 421 if not test_email:
422 422 h.flash(_('Please enter email address'), category='error')
423 423 return redirect(url('admin_settings_email'))
424 424
425 425 email_kwargs = {
426 426 'date': datetime.datetime.now(),
427 427 'user': c.rhodecode_user,
428 428 'rhodecode_version': c.rhodecode_version
429 429 }
430 430
431 431 (subject, headers, email_body,
432 432 email_body_plaintext) = EmailNotificationModel().render_email(
433 433 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
434 434
435 435 recipients = [test_email] if test_email else None
436 436
437 437 run_task(tasks.send_email, recipients, subject,
438 438 email_body_plaintext, email_body)
439 439
440 440 h.flash(_('Send email task created'), category='success')
441 441 return redirect(url('admin_settings_email'))
442 442
443 443 @HasPermissionAllDecorator('hg.admin')
444 444 def settings_email(self):
445 445 """GET /admin/settings/email: All items in the collection"""
446 446 # url('admin_settings_email')
447 447 c.active = 'email'
448 448 c.rhodecode_ini = rhodecode.CONFIG
449 449
450 450 return htmlfill.render(
451 451 render('admin/settings/settings.html'),
452 452 defaults=self._form_defaults(),
453 453 encoding="UTF-8",
454 454 force_defaults=False)
455 455
456 456 @HasPermissionAllDecorator('hg.admin')
457 457 @auth.CSRFRequired()
458 458 def settings_hooks_update(self):
459 459 """POST or DELETE /admin/settings/hooks: All items in the collection"""
460 460 # url('admin_settings_hooks')
461 461 c.active = 'hooks'
462 462 if c.visual.allow_custom_hooks_settings:
463 463 ui_key = request.POST.get('new_hook_ui_key')
464 464 ui_value = request.POST.get('new_hook_ui_value')
465 465
466 466 hook_id = request.POST.get('hook_id')
467 467 new_hook = False
468 468
469 469 model = SettingsModel()
470 470 try:
471 471 if ui_value and ui_key:
472 472 model.create_or_update_hook(ui_key, ui_value)
473 473 h.flash(_('Added new hook'), category='success')
474 474 new_hook = True
475 475 elif hook_id:
476 476 RhodeCodeUi.delete(hook_id)
477 477 Session().commit()
478 478
479 479 # check for edits
480 480 update = False
481 481 _d = request.POST.dict_of_lists()
482 482 for k, v in zip(_d.get('hook_ui_key', []),
483 483 _d.get('hook_ui_value_new', [])):
484 484 model.create_or_update_hook(k, v)
485 485 update = True
486 486
487 487 if update and not new_hook:
488 488 h.flash(_('Updated hooks'), category='success')
489 489 Session().commit()
490 490 except Exception:
491 491 log.exception("Exception during hook creation")
492 492 h.flash(_('Error occurred during hook creation'),
493 493 category='error')
494 494
495 495 return redirect(url('admin_settings_hooks'))
496 496
497 497 @HasPermissionAllDecorator('hg.admin')
498 498 def settings_hooks(self):
499 499 """GET /admin/settings/hooks: All items in the collection"""
500 500 # url('admin_settings_hooks')
501 501 c.active = 'hooks'
502 502
503 503 model = SettingsModel()
504 504 c.hooks = model.get_builtin_hooks()
505 505 c.custom_hooks = model.get_custom_hooks()
506 506
507 507 return htmlfill.render(
508 508 render('admin/settings/settings.html'),
509 509 defaults=self._form_defaults(),
510 510 encoding="UTF-8",
511 511 force_defaults=False)
512 512
513 513 @HasPermissionAllDecorator('hg.admin')
514 514 def settings_search(self):
515 515 """GET /admin/settings/search: All items in the collection"""
516 516 # url('admin_settings_search')
517 517 c.active = 'search'
518 518
519 519 from rhodecode.lib.index import searcher_from_config
520 520 searcher = searcher_from_config(config)
521 521 c.statistics = searcher.statistics()
522 522
523 523 return render('admin/settings/settings.html')
524 524
525 525 @HasPermissionAllDecorator('hg.admin')
526 526 def settings_system(self):
527 527 """GET /admin/settings/system: All items in the collection"""
528 528 # url('admin_settings_system')
529 529 snapshot = str2bool(request.GET.get('snapshot'))
530 530 c.active = 'system'
531 531
532 532 defaults = self._form_defaults()
533 533 c.rhodecode_ini = rhodecode.CONFIG
534 534 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
535 535 server_info = ScmModel().get_server_info(request.environ)
536 536 for key, val in server_info.iteritems():
537 537 setattr(c, key, val)
538 538
539 539 if c.disk['percent'] > 90:
540 540 h.flash(h.literal(_(
541 541 'Critical: your disk space is very low <b>%s%%</b> used' %
542 542 c.disk['percent'])), 'error')
543 543 elif c.disk['percent'] > 70:
544 544 h.flash(h.literal(_(
545 545 'Warning: your disk space is running low <b>%s%%</b> used' %
546 546 c.disk['percent'])), 'warning')
547 547
548 548 try:
549 549 c.uptime_age = h._age(
550 550 h.time_to_datetime(c.boot_time), False, show_suffix=False)
551 551 except TypeError:
552 552 c.uptime_age = c.boot_time
553 553
554 554 try:
555 555 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
556 556 h.format_byte_size_binary(c.memory['used']),
557 557 h.format_byte_size_binary(c.memory['total']),
558 558 c.memory['percent2'],
559 559 c.memory['percent'],
560 560 ' %s' % c.memory['error'] if 'error' in c.memory else '')
561 561 except TypeError:
562 562 c.system_memory = 'NOT AVAILABLE'
563 563
564 564 rhodecode_ini_safe = rhodecode.CONFIG.copy()
565 565 blacklist = [
566 566 'rhodecode_license_key',
567 567 'routes.map',
568 568 'pylons.h',
569 569 'pylons.app_globals',
570 570 'pylons.environ_config',
571 571 'sqlalchemy.db1.url',
572 572 ('app_conf', 'sqlalchemy.db1.url')
573 573 ]
574 574 for k in blacklist:
575 575 if isinstance(k, tuple):
576 576 section, key = k
577 577 if section in rhodecode_ini_safe:
578 578 rhodecode_ini_safe[section].pop(key, None)
579 579 else:
580 580 rhodecode_ini_safe.pop(k, None)
581 581
582 582 c.rhodecode_ini_safe = rhodecode_ini_safe
583 583
584 584 # TODO: marcink, figure out how to allow only selected users to do this
585 585 c.allowed_to_snapshot = False
586 586
587 587 if snapshot:
588 588 if c.allowed_to_snapshot:
589 589 return render('admin/settings/settings_system_snapshot.html')
590 590 else:
591 591 h.flash('You are not allowed to do this', category='warning')
592 592
593 593 return htmlfill.render(
594 594 render('admin/settings/settings.html'),
595 595 defaults=defaults,
596 596 encoding="UTF-8",
597 597 force_defaults=False)
598 598
599 599 @staticmethod
600 600 def get_update_data(update_url):
601 601 """Return the JSON update data."""
602 602 ver = rhodecode.__version__
603 603 log.debug('Checking for upgrade on `%s` server', update_url)
604 604 opener = urllib2.build_opener()
605 605 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
606 606 response = opener.open(update_url)
607 607 response_data = response.read()
608 608 data = json.loads(response_data)
609 609
610 610 return data
611 611
612 612 @HasPermissionAllDecorator('hg.admin')
613 613 def settings_system_update(self):
614 614 """GET /admin/settings/system/updates: All items in the collection"""
615 615 # url('admin_settings_system_update')
616 616 defaults = self._form_defaults()
617 617 update_url = defaults.get('rhodecode_update_url', '')
618 618
619 619 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
620 620 try:
621 621 data = self.get_update_data(update_url)
622 622 except urllib2.URLError as e:
623 623 log.exception("Exception contacting upgrade server")
624 624 return _err('Failed to contact upgrade server: %r' % e)
625 625 except ValueError as e:
626 626 log.exception("Bad data sent from update server")
627 627 return _err('Bad data sent from update server')
628 628
629 629 latest = data['versions'][0]
630 630
631 631 c.update_url = update_url
632 632 c.latest_data = latest
633 633 c.latest_ver = latest['version']
634 634 c.cur_ver = rhodecode.__version__
635 635 c.should_upgrade = False
636 636
637 637 if (packaging.version.Version(c.latest_ver) >
638 638 packaging.version.Version(c.cur_ver)):
639 639 c.should_upgrade = True
640 640 c.important_notices = latest['general']
641 641
642 642 return render('admin/settings/settings_system_update.html')
643 643
644 644 @HasPermissionAllDecorator('hg.admin')
645 645 def settings_supervisor(self):
646 646 c.rhodecode_ini = rhodecode.CONFIG
647 647 c.active = 'supervisor'
648 648
649 649 c.supervisor_procs = OrderedDict([
650 650 (SUPERVISOR_MASTER, {}),
651 651 ])
652 652
653 653 c.log_size = 10240
654 654 supervisor = SupervisorModel()
655 655
656 656 _connection = supervisor.get_connection(
657 657 c.rhodecode_ini.get('supervisor.uri'))
658 658 c.connection_error = None
659 659 try:
660 660 _connection.supervisor.getAllProcessInfo()
661 661 except Exception as e:
662 662 c.connection_error = str(e)
663 663 log.exception("Exception reading supervisor data")
664 664 return render('admin/settings/settings.html')
665 665
666 666 groupid = c.rhodecode_ini.get('supervisor.group_id')
667 667
668 668 # feed our group processes to the main
669 669 for proc in supervisor.get_group_processes(_connection, groupid):
670 670 c.supervisor_procs[proc['name']] = {}
671 671
672 672 for k in c.supervisor_procs.keys():
673 673 try:
674 674 # master process info
675 675 if k == SUPERVISOR_MASTER:
676 676 _data = supervisor.get_master_state(_connection)
677 677 _data['name'] = 'supervisor master'
678 678 _data['description'] = 'pid %s, id: %s, ver: %s' % (
679 679 _data['pid'], _data['id'], _data['ver'])
680 680 c.supervisor_procs[k] = _data
681 681 else:
682 682 procid = groupid + ":" + k
683 683 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
684 684 except Exception as e:
685 685 log.exception("Exception reading supervisor data")
686 686 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
687 687
688 688 return render('admin/settings/settings.html')
689 689
690 690 @HasPermissionAllDecorator('hg.admin')
691 691 def settings_supervisor_log(self, procid):
692 692 import rhodecode
693 693 c.rhodecode_ini = rhodecode.CONFIG
694 694 c.active = 'supervisor_tail'
695 695
696 696 supervisor = SupervisorModel()
697 697 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
698 698 groupid = c.rhodecode_ini.get('supervisor.group_id')
699 699 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
700 700
701 701 c.log_size = 10240
702 702 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
703 703 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
704 704
705 705 return render('admin/settings/settings.html')
706 706
707 707 @HasPermissionAllDecorator('hg.admin')
708 708 @auth.CSRFRequired()
709 709 def settings_labs_update(self):
710 710 """POST /admin/settings/labs: All items in the collection"""
711 711 # url('admin_settings/labs', method={'POST'})
712 712 c.active = 'labs'
713 713
714 714 application_form = LabsSettingsForm()()
715 715 try:
716 716 form_result = application_form.to_python(dict(request.POST))
717 717 except formencode.Invalid as errors:
718 718 h.flash(
719 719 _('Some form inputs contain invalid data.'),
720 720 category='error')
721 721 return htmlfill.render(
722 722 render('admin/settings/settings.html'),
723 723 defaults=errors.value,
724 724 errors=errors.error_dict or {},
725 725 prefix_error=False,
726 726 encoding='UTF-8',
727 727 force_defaults=False
728 728 )
729 729
730 730 try:
731 731 session = Session()
732 732 for setting in _LAB_SETTINGS:
733 733 setting_name = setting.key[len('rhodecode_'):]
734 734 sett = SettingsModel().create_or_update_setting(
735 735 setting_name, form_result[setting.key], setting.type)
736 736 session.add(sett)
737 737
738 738 except Exception:
739 739 log.exception('Exception while updating lab settings')
740 740 h.flash(_('Error occurred during updating labs settings'),
741 741 category='error')
742 742 else:
743 743 Session().commit()
744 744 SettingsModel().invalidate_settings_cache()
745 745 h.flash(_('Updated Labs settings'), category='success')
746 746 return redirect(url('admin_settings_labs'))
747 747
748 748 return htmlfill.render(
749 749 render('admin/settings/settings.html'),
750 750 defaults=self._form_defaults(),
751 751 encoding='UTF-8',
752 752 force_defaults=False)
753 753
754 754 @HasPermissionAllDecorator('hg.admin')
755 755 def settings_labs(self):
756 756 """GET /admin/settings/labs: All items in the collection"""
757 757 # url('admin_settings_labs')
758 758 if not c.labs_active:
759 759 redirect(url('admin_settings'))
760 760
761 761 c.active = 'labs'
762 762 c.lab_settings = _LAB_SETTINGS
763 763
764 764 return htmlfill.render(
765 765 render('admin/settings/settings.html'),
766 766 defaults=self._form_defaults(),
767 767 encoding='UTF-8',
768 768 force_defaults=False)
769 769
770 770 def _form_defaults(self):
771 771 defaults = SettingsModel().get_all_settings()
772 772 defaults.update(self._get_hg_ui_settings())
773 773 defaults.update({
774 774 'new_svn_branch': '',
775 775 'new_svn_tag': '',
776 776 })
777 777 return defaults
778 778
779 779
780 780 # :param key: name of the setting including the 'rhodecode_' prefix
781 781 # :param type: the RhodeCodeSetting type to use.
782 782 # :param group: the i18ned group in which we should dispaly this setting
783 783 # :param label: the i18ned label we should display for this setting
784 784 # :param help: the i18ned help we should dispaly for this setting
785 785 LabSetting = collections.namedtuple(
786 786 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
787 787
788 788
789 789 # This list has to be kept in sync with the form
790 790 # rhodecode.model.forms.LabsSettingsForm.
791 791 _LAB_SETTINGS = [
792 792 LabSetting(
793 793 key='rhodecode_proxy_subversion_http_requests',
794 794 type='bool',
795 795 group=lazy_ugettext('Subversion HTTP Support'),
796 796 label=lazy_ugettext('Proxy subversion HTTP requests'),
797 797 help='' # Do not translate the empty string!
798 798 ),
799 799 LabSetting(
800 800 key='rhodecode_subversion_http_server_url',
801 801 type='str',
802 802 group=lazy_ugettext('Subversion HTTP Server URL'),
803 803 label='', # Do not translate the empty string!
804 804 help=lazy_ugettext('e.g. http://localhost:8080/')
805 805 ),
806 806 ]
General Comments 0
You need to be logged in to leave comments. Login now