##// END OF EJS Templates
session: moved session cleanup to pyramid views.
marcink -
r1301:adb14d6f default
parent child Browse files
Show More
@@ -1,43 +1,50 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 from rhodecode.admin.navigation import NavigationRegistry
23 23 from rhodecode.config.routing import ADMIN_PREFIX
24 24 from rhodecode.lib.utils2 import str2bool
25 25
26 26
27 27 def includeme(config):
28 28 settings = config.get_settings()
29 29
30 30 # Create admin navigation registry and add it to the pyramid registry.
31 31 labs_active = str2bool(settings.get('labs_settings_active', False))
32 32 navigation_registry = NavigationRegistry(labs_active=labs_active)
33 33 config.registry.registerUtility(navigation_registry)
34 34
35 35 config.add_route(
36 36 name='admin_settings_open_source',
37 37 pattern=ADMIN_PREFIX + '/settings/open_source')
38 38 config.add_route(
39 39 name='admin_settings_vcs_svn_generate_cfg',
40 40 pattern=ADMIN_PREFIX + '/settings/vcs/svn_generate_cfg')
41 41
42 config.add_route(
43 name='admin_settings_sessions',
44 pattern=ADMIN_PREFIX + '/settings/sessions')
45 config.add_route(
46 name='admin_settings_sessions_cleanup',
47 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
48
42 49 # Scan module for configuration decorators.
43 50 config.scan()
@@ -1,128 +1,133 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 import logging
23 23 import collections
24 24
25 25 from pylons import url
26 26 from zope.interface import implementer
27 27
28 28 from rhodecode.admin.interfaces import IAdminNavigationRegistry
29 29 from rhodecode.lib.utils import get_registry
30 30 from rhodecode.translation import _
31 31
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
36 36
37 37
38 38 class NavEntry(object):
39 39 """
40 40 Represents an entry in the admin navigation.
41 41
42 42 :param key: Unique identifier used to store reference in an OrderedDict.
43 43 :param name: Display name, usually a translation string.
44 44 :param view_name: Name of the view, used generate the URL.
45 45 :param pyramid: Indicator to use pyramid for URL generation. This should
46 46 be removed as soon as we are fully migrated to pyramid.
47 47 """
48 48
49 49 def __init__(self, key, name, view_name, pyramid=False):
50 50 self.key = key
51 51 self.name = name
52 52 self.view_name = view_name
53 53 self.pyramid = pyramid
54 54
55 55 def generate_url(self, request):
56 56 if self.pyramid:
57 57 if hasattr(request, 'route_path'):
58 58 return request.route_path(self.view_name)
59 59 else:
60 60 # TODO: johbo: Remove this after migrating to pyramid.
61 61 # We need the pyramid request here to generate URLs to pyramid
62 62 # views from within pylons views.
63 63 from pyramid.threadlocal import get_current_request
64 64 pyramid_request = get_current_request()
65 65 return pyramid_request.route_path(self.view_name)
66 66 else:
67 67 return url(self.view_name)
68 68
69 69
70 70 @implementer(IAdminNavigationRegistry)
71 71 class NavigationRegistry(object):
72 72
73 73 _base_entries = [
74 74 NavEntry('global', _('Global'), 'admin_settings_global'),
75 75 NavEntry('vcs', _('VCS'), 'admin_settings_vcs'),
76 76 NavEntry('visual', _('Visual'), 'admin_settings_visual'),
77 77 NavEntry('mapping', _('Remap and Rescan'), 'admin_settings_mapping'),
78 78 NavEntry('issuetracker', _('Issue Tracker'),
79 79 'admin_settings_issuetracker'),
80 80 NavEntry('email', _('Email'), 'admin_settings_email'),
81 81 NavEntry('hooks', _('Hooks'), 'admin_settings_hooks'),
82 82 NavEntry('search', _('Full Text Search'), 'admin_settings_search'),
83
84
83 85 NavEntry('integrations', _('Integrations'),
84 86 'global_integrations_home', pyramid=True),
85 87 NavEntry('system', _('System Info'), 'admin_settings_system'),
86 NavEntry('session', _('User Sessions'), 'admin_settings_sessions'),
88
89
90 NavEntry('session', _('User Sessions'),
91 'admin_settings_sessions', pyramid=True),
87 92 NavEntry('open_source', _('Open Source Licenses'),
88 93 'admin_settings_open_source', pyramid=True),
89 94
90 95 # TODO: marcink: we disable supervisor now until the supervisor stats
91 96 # page is fixed in the nix configuration
92 97 # NavEntry('supervisor', _('Supervisor'), 'admin_settings_supervisor'),
93 98 ]
94 99
95 100 _labs_entry = NavEntry('labs', _('Labs'),
96 101 'admin_settings_labs')
97 102
98 103 def __init__(self, labs_active=False):
99 104 self._registered_entries = collections.OrderedDict([
100 105 (item.key, item) for item in self.__class__._base_entries
101 106 ])
102 107
103 108 if labs_active:
104 109 self.add_entry(self._labs_entry)
105 110
106 111 def add_entry(self, entry):
107 112 self._registered_entries[entry.key] = entry
108 113
109 114 def get_navlist(self, request):
110 115 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
111 116 for i in self._registered_entries.values()]
112 117 return navlist
113 118
114 119
115 120 def navigation_registry(request):
116 121 """
117 122 Helper that returns the admin navigation registry.
118 123 """
119 124 pyramid_registry = get_registry(request)
120 125 nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry)
121 126 return nav_registry
122 127
123 128
124 129 def navigation_list(request):
125 130 """
126 131 Helper that returns the admin navigation as list of NavListEntry objects.
127 132 """
128 133 return navigation_registry(request).get_navlist(request)
@@ -1,82 +1,145 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 import collections
22 22 import logging
23 23
24 24 from pylons import tmpl_context as c
25 25 from pyramid.view import view_config
26 from pyramid.httpexceptions import HTTPFound
27
28 from rhodecode.translation import _
29 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
26 30
27 31 from rhodecode.lib.auth import (
28 32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 33 from rhodecode.lib.utils import read_opensource_licenses
30 from rhodecode.svn_support.utils import generate_mod_dav_svn_config
31 from rhodecode.translation import _
34 from rhodecode.lib.utils2 import safe_int
35 from rhodecode.lib import system_info
36 from rhodecode.lib import user_sessions
37
32 38
33 39 from .navigation import navigation_list
34 40
35 41
36 42 log = logging.getLogger(__name__)
37 43
38 44
39 45 class AdminSettingsView(object):
40 46
41 47 def __init__(self, context, request):
42 48 self.request = request
43 49 self.context = context
44 50 self.session = request.session
45 51 self._rhodecode_user = request.user
46 52
47 53 @LoginRequired()
48 54 @HasPermissionAllDecorator('hg.admin')
49 55 @view_config(
50 56 route_name='admin_settings_open_source', request_method='GET',
51 57 renderer='rhodecode:templates/admin/settings/settings.mako')
52 58 def open_source_licenses(self):
53 59 c.active = 'open_source'
54 60 c.navlist = navigation_list(self.request)
55 61 c.opensource_licenses = collections.OrderedDict(
56 62 sorted(read_opensource_licenses().items(), key=lambda t: t[0]))
57 63
58 64 return {}
59 65
60 66 @LoginRequired()
61 67 @CSRFRequired()
62 68 @HasPermissionAllDecorator('hg.admin')
63 69 @view_config(
64 70 route_name='admin_settings_vcs_svn_generate_cfg',
65 71 request_method='POST', renderer='json')
66 72 def vcs_svn_generate_config(self):
67 73 try:
68 74 generate_mod_dav_svn_config(self.request.registry)
69 75 msg = {
70 76 'message': _('Apache configuration for Subversion generated.'),
71 77 'level': 'success',
72 78 }
73 79 except Exception:
74 80 log.exception(
75 'Exception while generating the Apache configuration for Subversion.')
81 'Exception while generating the Apache '
82 'configuration for Subversion.')
76 83 msg = {
77 84 'message': _('Failed to generate the Apache configuration for Subversion.'),
78 85 'level': 'error',
79 86 }
80 87
81 88 data = {'message': msg}
82 89 return data
90
91 @LoginRequired()
92 @HasPermissionAllDecorator('hg.admin')
93 @view_config(
94 route_name='admin_settings_sessions', request_method='GET',
95 renderer='rhodecode:templates/admin/settings/settings.mako')
96 def settings_sessions(self):
97 c.active = 'sessions'
98 c.navlist = navigation_list(self.request)
99
100 c.cleanup_older_days = 60
101 older_than_seconds = 24 * 60 * 60 * 24 * c.cleanup_older_days
102
103 config = system_info.rhodecode_config().get_value()['value']['config']
104 c.session_model = user_sessions.get_session_handler(
105 config.get('beaker.session.type', 'memory'))(config)
106
107 c.session_conf = c.session_model.config
108 c.session_count = c.session_model.get_count()
109 c.session_expired_count = c.session_model.get_expired_count(
110 older_than_seconds)
111
112 return {}
113
114 @LoginRequired()
115 @HasPermissionAllDecorator('hg.admin')
116 @view_config(
117 route_name='admin_settings_sessions_cleanup', request_method='POST')
118 def settings_sessions_cleanup(self):
119
120 expire_days = safe_int(self.request.params.get('expire_days'))
121
122 if expire_days is None:
123 expire_days = 60
124
125 older_than_seconds = 24 * 60 * 60 * 24 * expire_days
126
127 config = system_info.rhodecode_config().get_value()['value']['config']
128 session_model = user_sessions.get_session_handler(
129 config.get('beaker.session.type', 'memory'))(config)
130
131 try:
132 session_model.clean_sessions(
133 older_than_seconds=older_than_seconds)
134 self.request.session.flash(
135 _('Cleaned up old sessions'), queue='success')
136 except user_sessions.CleanupCommand as msg:
137 self.request.session.flash(msg, category='warning')
138 except Exception as e:
139 log.exception('Failed session cleanup')
140 self.request.session.flash(
141 _('Failed to cleanup up old sessions'), queue='error')
142
143 redirect_to = self.request.resource_path(
144 self.context, route_name='admin_settings_sessions')
145 return HTTPFound(redirect_to)
@@ -1,1179 +1,1173 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 Routes configuration
23 23
24 24 The more specific and detailed routes should be defined first so they
25 25 may take precedent over the more generic routes. For more information
26 26 refer to the routes manual at http://routes.groovie.org/docs/
27 27
28 28 IMPORTANT: if you change any routing here, make sure to take a look at lib/base.py
29 29 and _route_name variable which uses some of stored naming here to do redirects.
30 30 """
31 31 import os
32 32 import re
33 33 from routes import Mapper
34 34
35 35 from rhodecode.config import routing_links
36 36
37 37 # prefix for non repository related links needs to be prefixed with `/`
38 38 ADMIN_PREFIX = '/_admin'
39 39 STATIC_FILE_PREFIX = '/_static'
40 40
41 41 # Default requirements for URL parts
42 42 URL_NAME_REQUIREMENTS = {
43 43 # group name can have a slash in them, but they must not end with a slash
44 44 'group_name': r'.*?[^/]',
45 45 'repo_group_name': r'.*?[^/]',
46 46 # repo names can have a slash in them, but they must not end with a slash
47 47 'repo_name': r'.*?[^/]',
48 48 # file path eats up everything at the end
49 49 'f_path': r'.*',
50 50 # reference types
51 51 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
52 52 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
53 53 }
54 54
55 55
56 56 def add_route_requirements(route_path, requirements):
57 57 """
58 58 Adds regex requirements to pyramid routes using a mapping dict
59 59
60 60 >>> add_route_requirements('/{action}/{id}', {'id': r'\d+'})
61 61 '/{action}/{id:\d+}'
62 62
63 63 """
64 64 for key, regex in requirements.items():
65 65 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
66 66 return route_path
67 67
68 68
69 69 class JSRoutesMapper(Mapper):
70 70 """
71 71 Wrapper for routes.Mapper to make pyroutes compatible url definitions
72 72 """
73 73 _named_route_regex = re.compile(r'^[a-z-_0-9A-Z]+$')
74 74 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
75 75 def __init__(self, *args, **kw):
76 76 super(JSRoutesMapper, self).__init__(*args, **kw)
77 77 self._jsroutes = []
78 78
79 79 def connect(self, *args, **kw):
80 80 """
81 81 Wrapper for connect to take an extra argument jsroute=True
82 82
83 83 :param jsroute: boolean, if True will add the route to the pyroutes list
84 84 """
85 85 if kw.pop('jsroute', False):
86 86 if not self._named_route_regex.match(args[0]):
87 87 raise Exception('only named routes can be added to pyroutes')
88 88 self._jsroutes.append(args[0])
89 89
90 90 super(JSRoutesMapper, self).connect(*args, **kw)
91 91
92 92 def _extract_route_information(self, route):
93 93 """
94 94 Convert a route into tuple(name, path, args), eg:
95 95 ('user_profile', '/profile/%(username)s', ['username'])
96 96 """
97 97 routepath = route.routepath
98 98 def replace(matchobj):
99 99 if matchobj.group(1):
100 100 return "%%(%s)s" % matchobj.group(1).split(':')[0]
101 101 else:
102 102 return "%%(%s)s" % matchobj.group(2)
103 103
104 104 routepath = self._argument_prog.sub(replace, routepath)
105 105 return (
106 106 route.name,
107 107 routepath,
108 108 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
109 109 for arg in self._argument_prog.findall(route.routepath)]
110 110 )
111 111
112 112 def jsroutes(self):
113 113 """
114 114 Return a list of pyroutes.js compatible routes
115 115 """
116 116 for route_name in self._jsroutes:
117 117 yield self._extract_route_information(self._routenames[route_name])
118 118
119 119
120 120 def make_map(config):
121 121 """Create, configure and return the routes Mapper"""
122 122 rmap = JSRoutesMapper(directory=config['pylons.paths']['controllers'],
123 123 always_scan=config['debug'])
124 124 rmap.minimization = False
125 125 rmap.explicit = False
126 126
127 127 from rhodecode.lib.utils2 import str2bool
128 128 from rhodecode.model import repo, repo_group
129 129
130 130 def check_repo(environ, match_dict):
131 131 """
132 132 check for valid repository for proper 404 handling
133 133
134 134 :param environ:
135 135 :param match_dict:
136 136 """
137 137 repo_name = match_dict.get('repo_name')
138 138
139 139 if match_dict.get('f_path'):
140 140 # fix for multiple initial slashes that causes errors
141 141 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
142 142 repo_model = repo.RepoModel()
143 143 by_name_match = repo_model.get_by_repo_name(repo_name)
144 144 # if we match quickly from database, short circuit the operation,
145 145 # and validate repo based on the type.
146 146 if by_name_match:
147 147 return True
148 148
149 149 by_id_match = repo_model.get_repo_by_id(repo_name)
150 150 if by_id_match:
151 151 repo_name = by_id_match.repo_name
152 152 match_dict['repo_name'] = repo_name
153 153 return True
154 154
155 155 return False
156 156
157 157 def check_group(environ, match_dict):
158 158 """
159 159 check for valid repository group path for proper 404 handling
160 160
161 161 :param environ:
162 162 :param match_dict:
163 163 """
164 164 repo_group_name = match_dict.get('group_name')
165 165 repo_group_model = repo_group.RepoGroupModel()
166 166 by_name_match = repo_group_model.get_by_group_name(repo_group_name)
167 167 if by_name_match:
168 168 return True
169 169
170 170 return False
171 171
172 172 def check_user_group(environ, match_dict):
173 173 """
174 174 check for valid user group for proper 404 handling
175 175
176 176 :param environ:
177 177 :param match_dict:
178 178 """
179 179 return True
180 180
181 181 def check_int(environ, match_dict):
182 182 return match_dict.get('id').isdigit()
183 183
184 184
185 185 #==========================================================================
186 186 # CUSTOM ROUTES HERE
187 187 #==========================================================================
188 188
189 189 # MAIN PAGE
190 190 rmap.connect('home', '/', controller='home', action='index', jsroute=True)
191 191 rmap.connect('goto_switcher_data', '/_goto_data', controller='home',
192 192 action='goto_switcher_data')
193 193 rmap.connect('repo_list_data', '/_repos', controller='home',
194 194 action='repo_list_data')
195 195
196 196 rmap.connect('user_autocomplete_data', '/_users', controller='home',
197 197 action='user_autocomplete_data', jsroute=True)
198 198 rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
199 199 action='user_group_autocomplete_data', jsroute=True)
200 200
201 201 rmap.connect(
202 202 'user_profile', '/_profiles/{username}', controller='users',
203 203 action='user_profile')
204 204
205 205 # TODO: johbo: Static links, to be replaced by our redirection mechanism
206 206 rmap.connect('rst_help',
207 207 'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
208 208 _static=True)
209 209 rmap.connect('markdown_help',
210 210 'http://daringfireball.net/projects/markdown/syntax',
211 211 _static=True)
212 212 rmap.connect('rhodecode_official', 'https://rhodecode.com', _static=True)
213 213 rmap.connect('rhodecode_support', 'https://rhodecode.com/help/', _static=True)
214 214 rmap.connect('rhodecode_translations', 'https://rhodecode.com/translate/enterprise', _static=True)
215 215 # TODO: anderson - making this a static link since redirect won't play
216 216 # nice with POST requests
217 217 rmap.connect('enterprise_license_convert_from_old',
218 218 'https://rhodecode.com/u/license-upgrade',
219 219 _static=True)
220 220
221 221 routing_links.connect_redirection_links(rmap)
222 222
223 223 rmap.connect('ping', '%s/ping' % (ADMIN_PREFIX,), controller='home', action='ping')
224 224 rmap.connect('error_test', '%s/error_test' % (ADMIN_PREFIX,), controller='home', action='error_test')
225 225
226 226 # ADMIN REPOSITORY ROUTES
227 227 with rmap.submapper(path_prefix=ADMIN_PREFIX,
228 228 controller='admin/repos') as m:
229 229 m.connect('repos', '/repos',
230 230 action='create', conditions={'method': ['POST']})
231 231 m.connect('repos', '/repos',
232 232 action='index', conditions={'method': ['GET']})
233 233 m.connect('new_repo', '/create_repository', jsroute=True,
234 234 action='create_repository', conditions={'method': ['GET']})
235 235 m.connect('/repos/{repo_name}',
236 236 action='update', conditions={'method': ['PUT'],
237 237 'function': check_repo},
238 238 requirements=URL_NAME_REQUIREMENTS)
239 239 m.connect('delete_repo', '/repos/{repo_name}',
240 240 action='delete', conditions={'method': ['DELETE']},
241 241 requirements=URL_NAME_REQUIREMENTS)
242 242 m.connect('repo', '/repos/{repo_name}',
243 243 action='show', conditions={'method': ['GET'],
244 244 'function': check_repo},
245 245 requirements=URL_NAME_REQUIREMENTS)
246 246
247 247 # ADMIN REPOSITORY GROUPS ROUTES
248 248 with rmap.submapper(path_prefix=ADMIN_PREFIX,
249 249 controller='admin/repo_groups') as m:
250 250 m.connect('repo_groups', '/repo_groups',
251 251 action='create', conditions={'method': ['POST']})
252 252 m.connect('repo_groups', '/repo_groups',
253 253 action='index', conditions={'method': ['GET']})
254 254 m.connect('new_repo_group', '/repo_groups/new',
255 255 action='new', conditions={'method': ['GET']})
256 256 m.connect('update_repo_group', '/repo_groups/{group_name}',
257 257 action='update', conditions={'method': ['PUT'],
258 258 'function': check_group},
259 259 requirements=URL_NAME_REQUIREMENTS)
260 260
261 261 # EXTRAS REPO GROUP ROUTES
262 262 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
263 263 action='edit',
264 264 conditions={'method': ['GET'], 'function': check_group},
265 265 requirements=URL_NAME_REQUIREMENTS)
266 266 m.connect('edit_repo_group', '/repo_groups/{group_name}/edit',
267 267 action='edit',
268 268 conditions={'method': ['PUT'], 'function': check_group},
269 269 requirements=URL_NAME_REQUIREMENTS)
270 270
271 271 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
272 272 action='edit_repo_group_advanced',
273 273 conditions={'method': ['GET'], 'function': check_group},
274 274 requirements=URL_NAME_REQUIREMENTS)
275 275 m.connect('edit_repo_group_advanced', '/repo_groups/{group_name}/edit/advanced',
276 276 action='edit_repo_group_advanced',
277 277 conditions={'method': ['PUT'], 'function': check_group},
278 278 requirements=URL_NAME_REQUIREMENTS)
279 279
280 280 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
281 281 action='edit_repo_group_perms',
282 282 conditions={'method': ['GET'], 'function': check_group},
283 283 requirements=URL_NAME_REQUIREMENTS)
284 284 m.connect('edit_repo_group_perms', '/repo_groups/{group_name}/edit/permissions',
285 285 action='update_perms',
286 286 conditions={'method': ['PUT'], 'function': check_group},
287 287 requirements=URL_NAME_REQUIREMENTS)
288 288
289 289 m.connect('delete_repo_group', '/repo_groups/{group_name}',
290 290 action='delete', conditions={'method': ['DELETE'],
291 291 'function': check_group},
292 292 requirements=URL_NAME_REQUIREMENTS)
293 293
294 294 # ADMIN USER ROUTES
295 295 with rmap.submapper(path_prefix=ADMIN_PREFIX,
296 296 controller='admin/users') as m:
297 297 m.connect('users', '/users',
298 298 action='create', conditions={'method': ['POST']})
299 299 m.connect('users', '/users',
300 300 action='index', conditions={'method': ['GET']})
301 301 m.connect('new_user', '/users/new',
302 302 action='new', conditions={'method': ['GET']})
303 303 m.connect('update_user', '/users/{user_id}',
304 304 action='update', conditions={'method': ['PUT']})
305 305 m.connect('delete_user', '/users/{user_id}',
306 306 action='delete', conditions={'method': ['DELETE']})
307 307 m.connect('edit_user', '/users/{user_id}/edit',
308 308 action='edit', conditions={'method': ['GET']}, jsroute=True)
309 309 m.connect('user', '/users/{user_id}',
310 310 action='show', conditions={'method': ['GET']})
311 311 m.connect('force_password_reset_user', '/users/{user_id}/password_reset',
312 312 action='reset_password', conditions={'method': ['POST']})
313 313 m.connect('create_personal_repo_group', '/users/{user_id}/create_repo_group',
314 314 action='create_personal_repo_group', conditions={'method': ['POST']})
315 315
316 316 # EXTRAS USER ROUTES
317 317 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
318 318 action='edit_advanced', conditions={'method': ['GET']})
319 319 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
320 320 action='update_advanced', conditions={'method': ['PUT']})
321 321
322 322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 323 action='edit_auth_tokens', conditions={'method': ['GET']})
324 324 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
325 325 action='add_auth_token', conditions={'method': ['PUT']})
326 326 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
327 327 action='delete_auth_token', conditions={'method': ['DELETE']})
328 328
329 329 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
330 330 action='edit_global_perms', conditions={'method': ['GET']})
331 331 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
332 332 action='update_global_perms', conditions={'method': ['PUT']})
333 333
334 334 m.connect('edit_user_perms_summary', '/users/{user_id}/edit/permissions_summary',
335 335 action='edit_perms_summary', conditions={'method': ['GET']})
336 336
337 337 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
338 338 action='edit_emails', conditions={'method': ['GET']})
339 339 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
340 340 action='add_email', conditions={'method': ['PUT']})
341 341 m.connect('edit_user_emails', '/users/{user_id}/edit/emails',
342 342 action='delete_email', conditions={'method': ['DELETE']})
343 343
344 344 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
345 345 action='edit_ips', conditions={'method': ['GET']})
346 346 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
347 347 action='add_ip', conditions={'method': ['PUT']})
348 348 m.connect('edit_user_ips', '/users/{user_id}/edit/ips',
349 349 action='delete_ip', conditions={'method': ['DELETE']})
350 350
351 351 # ADMIN USER GROUPS REST ROUTES
352 352 with rmap.submapper(path_prefix=ADMIN_PREFIX,
353 353 controller='admin/user_groups') as m:
354 354 m.connect('users_groups', '/user_groups',
355 355 action='create', conditions={'method': ['POST']})
356 356 m.connect('users_groups', '/user_groups',
357 357 action='index', conditions={'method': ['GET']})
358 358 m.connect('new_users_group', '/user_groups/new',
359 359 action='new', conditions={'method': ['GET']})
360 360 m.connect('update_users_group', '/user_groups/{user_group_id}',
361 361 action='update', conditions={'method': ['PUT']})
362 362 m.connect('delete_users_group', '/user_groups/{user_group_id}',
363 363 action='delete', conditions={'method': ['DELETE']})
364 364 m.connect('edit_users_group', '/user_groups/{user_group_id}/edit',
365 365 action='edit', conditions={'method': ['GET']},
366 366 function=check_user_group)
367 367
368 368 # EXTRAS USER GROUP ROUTES
369 369 m.connect('edit_user_group_global_perms',
370 370 '/user_groups/{user_group_id}/edit/global_permissions',
371 371 action='edit_global_perms', conditions={'method': ['GET']})
372 372 m.connect('edit_user_group_global_perms',
373 373 '/user_groups/{user_group_id}/edit/global_permissions',
374 374 action='update_global_perms', conditions={'method': ['PUT']})
375 375 m.connect('edit_user_group_perms_summary',
376 376 '/user_groups/{user_group_id}/edit/permissions_summary',
377 377 action='edit_perms_summary', conditions={'method': ['GET']})
378 378
379 379 m.connect('edit_user_group_perms',
380 380 '/user_groups/{user_group_id}/edit/permissions',
381 381 action='edit_perms', conditions={'method': ['GET']})
382 382 m.connect('edit_user_group_perms',
383 383 '/user_groups/{user_group_id}/edit/permissions',
384 384 action='update_perms', conditions={'method': ['PUT']})
385 385
386 386 m.connect('edit_user_group_advanced',
387 387 '/user_groups/{user_group_id}/edit/advanced',
388 388 action='edit_advanced', conditions={'method': ['GET']})
389 389
390 390 m.connect('edit_user_group_members',
391 391 '/user_groups/{user_group_id}/edit/members', jsroute=True,
392 392 action='user_group_members', conditions={'method': ['GET']})
393 393
394 394 # ADMIN PERMISSIONS ROUTES
395 395 with rmap.submapper(path_prefix=ADMIN_PREFIX,
396 396 controller='admin/permissions') as m:
397 397 m.connect('admin_permissions_application', '/permissions/application',
398 398 action='permission_application_update', conditions={'method': ['POST']})
399 399 m.connect('admin_permissions_application', '/permissions/application',
400 400 action='permission_application', conditions={'method': ['GET']})
401 401
402 402 m.connect('admin_permissions_global', '/permissions/global',
403 403 action='permission_global_update', conditions={'method': ['POST']})
404 404 m.connect('admin_permissions_global', '/permissions/global',
405 405 action='permission_global', conditions={'method': ['GET']})
406 406
407 407 m.connect('admin_permissions_object', '/permissions/object',
408 408 action='permission_objects_update', conditions={'method': ['POST']})
409 409 m.connect('admin_permissions_object', '/permissions/object',
410 410 action='permission_objects', conditions={'method': ['GET']})
411 411
412 412 m.connect('admin_permissions_ips', '/permissions/ips',
413 413 action='permission_ips', conditions={'method': ['POST']})
414 414 m.connect('admin_permissions_ips', '/permissions/ips',
415 415 action='permission_ips', conditions={'method': ['GET']})
416 416
417 417 m.connect('admin_permissions_overview', '/permissions/overview',
418 418 action='permission_perms', conditions={'method': ['GET']})
419 419
420 420 # ADMIN DEFAULTS REST ROUTES
421 421 with rmap.submapper(path_prefix=ADMIN_PREFIX,
422 422 controller='admin/defaults') as m:
423 423 m.connect('admin_defaults_repositories', '/defaults/repositories',
424 424 action='update_repository_defaults', conditions={'method': ['POST']})
425 425 m.connect('admin_defaults_repositories', '/defaults/repositories',
426 426 action='index', conditions={'method': ['GET']})
427 427
428 428 # ADMIN DEBUG STYLE ROUTES
429 429 if str2bool(config.get('debug_style')):
430 430 with rmap.submapper(path_prefix=ADMIN_PREFIX + '/debug_style',
431 431 controller='debug_style') as m:
432 432 m.connect('debug_style_home', '',
433 433 action='index', conditions={'method': ['GET']})
434 434 m.connect('debug_style_template', '/t/{t_path}',
435 435 action='template', conditions={'method': ['GET']})
436 436
437 437 # ADMIN SETTINGS ROUTES
438 438 with rmap.submapper(path_prefix=ADMIN_PREFIX,
439 439 controller='admin/settings') as m:
440 440
441 441 # default
442 442 m.connect('admin_settings', '/settings',
443 443 action='settings_global_update',
444 444 conditions={'method': ['POST']})
445 445 m.connect('admin_settings', '/settings',
446 446 action='settings_global', conditions={'method': ['GET']})
447 447
448 448 m.connect('admin_settings_vcs', '/settings/vcs',
449 449 action='settings_vcs_update',
450 450 conditions={'method': ['POST']})
451 451 m.connect('admin_settings_vcs', '/settings/vcs',
452 452 action='settings_vcs',
453 453 conditions={'method': ['GET']})
454 454 m.connect('admin_settings_vcs', '/settings/vcs',
455 455 action='delete_svn_pattern',
456 456 conditions={'method': ['DELETE']})
457 457
458 458 m.connect('admin_settings_mapping', '/settings/mapping',
459 459 action='settings_mapping_update',
460 460 conditions={'method': ['POST']})
461 461 m.connect('admin_settings_mapping', '/settings/mapping',
462 462 action='settings_mapping', conditions={'method': ['GET']})
463 463
464 464 m.connect('admin_settings_global', '/settings/global',
465 465 action='settings_global_update',
466 466 conditions={'method': ['POST']})
467 467 m.connect('admin_settings_global', '/settings/global',
468 468 action='settings_global', conditions={'method': ['GET']})
469 469
470 470 m.connect('admin_settings_visual', '/settings/visual',
471 471 action='settings_visual_update',
472 472 conditions={'method': ['POST']})
473 473 m.connect('admin_settings_visual', '/settings/visual',
474 474 action='settings_visual', conditions={'method': ['GET']})
475 475
476 476 m.connect('admin_settings_issuetracker',
477 477 '/settings/issue-tracker', action='settings_issuetracker',
478 478 conditions={'method': ['GET']})
479 479 m.connect('admin_settings_issuetracker_save',
480 480 '/settings/issue-tracker/save',
481 481 action='settings_issuetracker_save',
482 482 conditions={'method': ['POST']})
483 483 m.connect('admin_issuetracker_test', '/settings/issue-tracker/test',
484 484 action='settings_issuetracker_test',
485 485 conditions={'method': ['POST']})
486 486 m.connect('admin_issuetracker_delete',
487 487 '/settings/issue-tracker/delete',
488 488 action='settings_issuetracker_delete',
489 489 conditions={'method': ['DELETE']})
490 490
491 491 m.connect('admin_settings_email', '/settings/email',
492 492 action='settings_email_update',
493 493 conditions={'method': ['POST']})
494 494 m.connect('admin_settings_email', '/settings/email',
495 495 action='settings_email', conditions={'method': ['GET']})
496 496
497 497 m.connect('admin_settings_hooks', '/settings/hooks',
498 498 action='settings_hooks_update',
499 499 conditions={'method': ['POST', 'DELETE']})
500 500 m.connect('admin_settings_hooks', '/settings/hooks',
501 501 action='settings_hooks', conditions={'method': ['GET']})
502 502
503 503 m.connect('admin_settings_search', '/settings/search',
504 504 action='settings_search', conditions={'method': ['GET']})
505 505
506 506 m.connect('admin_settings_system', '/settings/system',
507 507 action='settings_system', conditions={'method': ['GET']})
508 508
509 509 m.connect('admin_settings_system_update', '/settings/system/updates',
510 510 action='settings_system_update', conditions={'method': ['GET']})
511 511
512 m.connect('admin_settings_sessions', '/settings/sessions',
513 action='settings_sessions', conditions={'method': ['GET']})
514
515 m.connect('admin_settings_sessions_cleanup', '/settings/sessions/cleanup',
516 action='settings_sessions_cleanup', conditions={'method': ['POST']})
517
518 512 m.connect('admin_settings_supervisor', '/settings/supervisor',
519 513 action='settings_supervisor', conditions={'method': ['GET']})
520 514 m.connect('admin_settings_supervisor_log', '/settings/supervisor/{procid}/log',
521 515 action='settings_supervisor_log', conditions={'method': ['GET']})
522 516
523 517 m.connect('admin_settings_labs', '/settings/labs',
524 518 action='settings_labs_update',
525 519 conditions={'method': ['POST']})
526 520 m.connect('admin_settings_labs', '/settings/labs',
527 521 action='settings_labs', conditions={'method': ['GET']})
528 522
529 523 # ADMIN MY ACCOUNT
530 524 with rmap.submapper(path_prefix=ADMIN_PREFIX,
531 525 controller='admin/my_account') as m:
532 526
533 527 m.connect('my_account', '/my_account',
534 528 action='my_account', conditions={'method': ['GET']})
535 529 m.connect('my_account_edit', '/my_account/edit',
536 530 action='my_account_edit', conditions={'method': ['GET']})
537 531 m.connect('my_account', '/my_account',
538 532 action='my_account_update', conditions={'method': ['POST']})
539 533
540 534 m.connect('my_account_password', '/my_account/password',
541 535 action='my_account_password', conditions={'method': ['GET', 'POST']})
542 536
543 537 m.connect('my_account_repos', '/my_account/repos',
544 538 action='my_account_repos', conditions={'method': ['GET']})
545 539
546 540 m.connect('my_account_watched', '/my_account/watched',
547 541 action='my_account_watched', conditions={'method': ['GET']})
548 542
549 543 m.connect('my_account_pullrequests', '/my_account/pull_requests',
550 544 action='my_account_pullrequests', conditions={'method': ['GET']})
551 545
552 546 m.connect('my_account_perms', '/my_account/perms',
553 547 action='my_account_perms', conditions={'method': ['GET']})
554 548
555 549 m.connect('my_account_emails', '/my_account/emails',
556 550 action='my_account_emails', conditions={'method': ['GET']})
557 551 m.connect('my_account_emails', '/my_account/emails',
558 552 action='my_account_emails_add', conditions={'method': ['POST']})
559 553 m.connect('my_account_emails', '/my_account/emails',
560 554 action='my_account_emails_delete', conditions={'method': ['DELETE']})
561 555
562 556 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
563 557 action='my_account_auth_tokens', conditions={'method': ['GET']})
564 558 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
565 559 action='my_account_auth_tokens_add', conditions={'method': ['POST']})
566 560 m.connect('my_account_auth_tokens', '/my_account/auth_tokens',
567 561 action='my_account_auth_tokens_delete', conditions={'method': ['DELETE']})
568 562 m.connect('my_account_notifications', '/my_account/notifications',
569 563 action='my_notifications',
570 564 conditions={'method': ['GET']})
571 565 m.connect('my_account_notifications_toggle_visibility',
572 566 '/my_account/toggle_visibility',
573 567 action='my_notifications_toggle_visibility',
574 568 conditions={'method': ['POST']})
575 569 m.connect('my_account_notifications_test_channelstream',
576 570 '/my_account/test_channelstream',
577 571 action='my_account_notifications_test_channelstream',
578 572 conditions={'method': ['POST']})
579 573
580 574 # NOTIFICATION REST ROUTES
581 575 with rmap.submapper(path_prefix=ADMIN_PREFIX,
582 576 controller='admin/notifications') as m:
583 577 m.connect('notifications', '/notifications',
584 578 action='index', conditions={'method': ['GET']})
585 579 m.connect('notifications_mark_all_read', '/notifications/mark_all_read',
586 580 action='mark_all_read', conditions={'method': ['POST']})
587 581 m.connect('/notifications/{notification_id}',
588 582 action='update', conditions={'method': ['PUT']})
589 583 m.connect('/notifications/{notification_id}',
590 584 action='delete', conditions={'method': ['DELETE']})
591 585 m.connect('notification', '/notifications/{notification_id}',
592 586 action='show', conditions={'method': ['GET']})
593 587
594 588 # ADMIN GIST
595 589 with rmap.submapper(path_prefix=ADMIN_PREFIX,
596 590 controller='admin/gists') as m:
597 591 m.connect('gists', '/gists',
598 592 action='create', conditions={'method': ['POST']})
599 593 m.connect('gists', '/gists', jsroute=True,
600 594 action='index', conditions={'method': ['GET']})
601 595 m.connect('new_gist', '/gists/new', jsroute=True,
602 596 action='new', conditions={'method': ['GET']})
603 597
604 598 m.connect('/gists/{gist_id}',
605 599 action='delete', conditions={'method': ['DELETE']})
606 600 m.connect('edit_gist', '/gists/{gist_id}/edit',
607 601 action='edit_form', conditions={'method': ['GET']})
608 602 m.connect('edit_gist', '/gists/{gist_id}/edit',
609 603 action='edit', conditions={'method': ['POST']})
610 604 m.connect(
611 605 'edit_gist_check_revision', '/gists/{gist_id}/edit/check_revision',
612 606 action='check_revision', conditions={'method': ['GET']})
613 607
614 608 m.connect('gist', '/gists/{gist_id}',
615 609 action='show', conditions={'method': ['GET']})
616 610 m.connect('gist_rev', '/gists/{gist_id}/{revision}',
617 611 revision='tip',
618 612 action='show', conditions={'method': ['GET']})
619 613 m.connect('formatted_gist', '/gists/{gist_id}/{revision}/{format}',
620 614 revision='tip',
621 615 action='show', conditions={'method': ['GET']})
622 616 m.connect('formatted_gist_file', '/gists/{gist_id}/{revision}/{format}/{f_path}',
623 617 revision='tip',
624 618 action='show', conditions={'method': ['GET']},
625 619 requirements=URL_NAME_REQUIREMENTS)
626 620
627 621 # ADMIN MAIN PAGES
628 622 with rmap.submapper(path_prefix=ADMIN_PREFIX,
629 623 controller='admin/admin') as m:
630 624 m.connect('admin_home', '', action='index')
631 625 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
632 626 action='add_repo')
633 627 m.connect(
634 628 'pull_requests_global_0', '/pull_requests/{pull_request_id:[0-9]+}',
635 629 action='pull_requests')
636 630 m.connect(
637 631 'pull_requests_global_1', '/pull-requests/{pull_request_id:[0-9]+}',
638 632 action='pull_requests')
639 633 m.connect(
640 634 'pull_requests_global', '/pull-request/{pull_request_id:[0-9]+}',
641 635 action='pull_requests')
642 636
643 637 # USER JOURNAL
644 638 rmap.connect('journal', '%s/journal' % (ADMIN_PREFIX,),
645 639 controller='journal', action='index')
646 640 rmap.connect('journal_rss', '%s/journal/rss' % (ADMIN_PREFIX,),
647 641 controller='journal', action='journal_rss')
648 642 rmap.connect('journal_atom', '%s/journal/atom' % (ADMIN_PREFIX,),
649 643 controller='journal', action='journal_atom')
650 644
651 645 rmap.connect('public_journal', '%s/public_journal' % (ADMIN_PREFIX,),
652 646 controller='journal', action='public_journal')
653 647
654 648 rmap.connect('public_journal_rss', '%s/public_journal/rss' % (ADMIN_PREFIX,),
655 649 controller='journal', action='public_journal_rss')
656 650
657 651 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % (ADMIN_PREFIX,),
658 652 controller='journal', action='public_journal_rss')
659 653
660 654 rmap.connect('public_journal_atom',
661 655 '%s/public_journal/atom' % (ADMIN_PREFIX,), controller='journal',
662 656 action='public_journal_atom')
663 657
664 658 rmap.connect('public_journal_atom_old',
665 659 '%s/public_journal_atom' % (ADMIN_PREFIX,), controller='journal',
666 660 action='public_journal_atom')
667 661
668 662 rmap.connect('toggle_following', '%s/toggle_following' % (ADMIN_PREFIX,),
669 663 controller='journal', action='toggle_following', jsroute=True,
670 664 conditions={'method': ['POST']})
671 665
672 666 # FULL TEXT SEARCH
673 667 rmap.connect('search', '%s/search' % (ADMIN_PREFIX,),
674 668 controller='search')
675 669 rmap.connect('search_repo_home', '/{repo_name}/search',
676 670 controller='search',
677 671 action='index',
678 672 conditions={'function': check_repo},
679 673 requirements=URL_NAME_REQUIREMENTS)
680 674
681 675 # FEEDS
682 676 rmap.connect('rss_feed_home', '/{repo_name}/feed/rss',
683 677 controller='feed', action='rss',
684 678 conditions={'function': check_repo},
685 679 requirements=URL_NAME_REQUIREMENTS)
686 680
687 681 rmap.connect('atom_feed_home', '/{repo_name}/feed/atom',
688 682 controller='feed', action='atom',
689 683 conditions={'function': check_repo},
690 684 requirements=URL_NAME_REQUIREMENTS)
691 685
692 686 #==========================================================================
693 687 # REPOSITORY ROUTES
694 688 #==========================================================================
695 689
696 690 rmap.connect('repo_creating_home', '/{repo_name}/repo_creating',
697 691 controller='admin/repos', action='repo_creating',
698 692 requirements=URL_NAME_REQUIREMENTS)
699 693 rmap.connect('repo_check_home', '/{repo_name}/crepo_check',
700 694 controller='admin/repos', action='repo_check',
701 695 requirements=URL_NAME_REQUIREMENTS)
702 696
703 697 rmap.connect('repo_stats', '/{repo_name}/repo_stats/{commit_id}',
704 698 controller='summary', action='repo_stats',
705 699 conditions={'function': check_repo},
706 700 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
707 701
708 702 rmap.connect('repo_refs_data', '/{repo_name}/refs-data',
709 703 controller='summary', action='repo_refs_data', jsroute=True,
710 704 requirements=URL_NAME_REQUIREMENTS)
711 705 rmap.connect('repo_refs_changelog_data', '/{repo_name}/refs-data-changelog',
712 706 controller='summary', action='repo_refs_changelog_data',
713 707 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
714 708 rmap.connect('repo_default_reviewers_data', '/{repo_name}/default-reviewers',
715 709 controller='summary', action='repo_default_reviewers_data',
716 710 jsroute=True, requirements=URL_NAME_REQUIREMENTS)
717 711
718 712 rmap.connect('changeset_home', '/{repo_name}/changeset/{revision}',
719 713 controller='changeset', revision='tip', jsroute=True,
720 714 conditions={'function': check_repo},
721 715 requirements=URL_NAME_REQUIREMENTS)
722 716 rmap.connect('changeset_children', '/{repo_name}/changeset_children/{revision}',
723 717 controller='changeset', revision='tip', action='changeset_children',
724 718 conditions={'function': check_repo},
725 719 requirements=URL_NAME_REQUIREMENTS)
726 720 rmap.connect('changeset_parents', '/{repo_name}/changeset_parents/{revision}',
727 721 controller='changeset', revision='tip', action='changeset_parents',
728 722 conditions={'function': check_repo},
729 723 requirements=URL_NAME_REQUIREMENTS)
730 724
731 725 # repo edit options
732 726 rmap.connect('edit_repo', '/{repo_name}/settings', jsroute=True,
733 727 controller='admin/repos', action='edit',
734 728 conditions={'method': ['GET'], 'function': check_repo},
735 729 requirements=URL_NAME_REQUIREMENTS)
736 730
737 731 rmap.connect('edit_repo_perms', '/{repo_name}/settings/permissions',
738 732 jsroute=True,
739 733 controller='admin/repos', action='edit_permissions',
740 734 conditions={'method': ['GET'], 'function': check_repo},
741 735 requirements=URL_NAME_REQUIREMENTS)
742 736 rmap.connect('edit_repo_perms_update', '/{repo_name}/settings/permissions',
743 737 controller='admin/repos', action='edit_permissions_update',
744 738 conditions={'method': ['PUT'], 'function': check_repo},
745 739 requirements=URL_NAME_REQUIREMENTS)
746 740
747 741 rmap.connect('edit_repo_fields', '/{repo_name}/settings/fields',
748 742 controller='admin/repos', action='edit_fields',
749 743 conditions={'method': ['GET'], 'function': check_repo},
750 744 requirements=URL_NAME_REQUIREMENTS)
751 745 rmap.connect('create_repo_fields', '/{repo_name}/settings/fields/new',
752 746 controller='admin/repos', action='create_repo_field',
753 747 conditions={'method': ['PUT'], 'function': check_repo},
754 748 requirements=URL_NAME_REQUIREMENTS)
755 749 rmap.connect('delete_repo_fields', '/{repo_name}/settings/fields/{field_id}',
756 750 controller='admin/repos', action='delete_repo_field',
757 751 conditions={'method': ['DELETE'], 'function': check_repo},
758 752 requirements=URL_NAME_REQUIREMENTS)
759 753
760 754 rmap.connect('edit_repo_advanced', '/{repo_name}/settings/advanced',
761 755 controller='admin/repos', action='edit_advanced',
762 756 conditions={'method': ['GET'], 'function': check_repo},
763 757 requirements=URL_NAME_REQUIREMENTS)
764 758
765 759 rmap.connect('edit_repo_advanced_locking', '/{repo_name}/settings/advanced/locking',
766 760 controller='admin/repos', action='edit_advanced_locking',
767 761 conditions={'method': ['PUT'], 'function': check_repo},
768 762 requirements=URL_NAME_REQUIREMENTS)
769 763 rmap.connect('toggle_locking', '/{repo_name}/settings/advanced/locking_toggle',
770 764 controller='admin/repos', action='toggle_locking',
771 765 conditions={'method': ['GET'], 'function': check_repo},
772 766 requirements=URL_NAME_REQUIREMENTS)
773 767
774 768 rmap.connect('edit_repo_advanced_journal', '/{repo_name}/settings/advanced/journal',
775 769 controller='admin/repos', action='edit_advanced_journal',
776 770 conditions={'method': ['PUT'], 'function': check_repo},
777 771 requirements=URL_NAME_REQUIREMENTS)
778 772
779 773 rmap.connect('edit_repo_advanced_fork', '/{repo_name}/settings/advanced/fork',
780 774 controller='admin/repos', action='edit_advanced_fork',
781 775 conditions={'method': ['PUT'], 'function': check_repo},
782 776 requirements=URL_NAME_REQUIREMENTS)
783 777
784 778 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
785 779 controller='admin/repos', action='edit_caches_form',
786 780 conditions={'method': ['GET'], 'function': check_repo},
787 781 requirements=URL_NAME_REQUIREMENTS)
788 782 rmap.connect('edit_repo_caches', '/{repo_name}/settings/caches',
789 783 controller='admin/repos', action='edit_caches',
790 784 conditions={'method': ['PUT'], 'function': check_repo},
791 785 requirements=URL_NAME_REQUIREMENTS)
792 786
793 787 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
794 788 controller='admin/repos', action='edit_remote_form',
795 789 conditions={'method': ['GET'], 'function': check_repo},
796 790 requirements=URL_NAME_REQUIREMENTS)
797 791 rmap.connect('edit_repo_remote', '/{repo_name}/settings/remote',
798 792 controller='admin/repos', action='edit_remote',
799 793 conditions={'method': ['PUT'], 'function': check_repo},
800 794 requirements=URL_NAME_REQUIREMENTS)
801 795
802 796 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
803 797 controller='admin/repos', action='edit_statistics_form',
804 798 conditions={'method': ['GET'], 'function': check_repo},
805 799 requirements=URL_NAME_REQUIREMENTS)
806 800 rmap.connect('edit_repo_statistics', '/{repo_name}/settings/statistics',
807 801 controller='admin/repos', action='edit_statistics',
808 802 conditions={'method': ['PUT'], 'function': check_repo},
809 803 requirements=URL_NAME_REQUIREMENTS)
810 804 rmap.connect('repo_settings_issuetracker',
811 805 '/{repo_name}/settings/issue-tracker',
812 806 controller='admin/repos', action='repo_issuetracker',
813 807 conditions={'method': ['GET'], 'function': check_repo},
814 808 requirements=URL_NAME_REQUIREMENTS)
815 809 rmap.connect('repo_issuetracker_test',
816 810 '/{repo_name}/settings/issue-tracker/test',
817 811 controller='admin/repos', action='repo_issuetracker_test',
818 812 conditions={'method': ['POST'], 'function': check_repo},
819 813 requirements=URL_NAME_REQUIREMENTS)
820 814 rmap.connect('repo_issuetracker_delete',
821 815 '/{repo_name}/settings/issue-tracker/delete',
822 816 controller='admin/repos', action='repo_issuetracker_delete',
823 817 conditions={'method': ['DELETE'], 'function': check_repo},
824 818 requirements=URL_NAME_REQUIREMENTS)
825 819 rmap.connect('repo_issuetracker_save',
826 820 '/{repo_name}/settings/issue-tracker/save',
827 821 controller='admin/repos', action='repo_issuetracker_save',
828 822 conditions={'method': ['POST'], 'function': check_repo},
829 823 requirements=URL_NAME_REQUIREMENTS)
830 824 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
831 825 controller='admin/repos', action='repo_settings_vcs_update',
832 826 conditions={'method': ['POST'], 'function': check_repo},
833 827 requirements=URL_NAME_REQUIREMENTS)
834 828 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
835 829 controller='admin/repos', action='repo_settings_vcs',
836 830 conditions={'method': ['GET'], 'function': check_repo},
837 831 requirements=URL_NAME_REQUIREMENTS)
838 832 rmap.connect('repo_vcs_settings', '/{repo_name}/settings/vcs',
839 833 controller='admin/repos', action='repo_delete_svn_pattern',
840 834 conditions={'method': ['DELETE'], 'function': check_repo},
841 835 requirements=URL_NAME_REQUIREMENTS)
842 836 rmap.connect('repo_pullrequest_settings', '/{repo_name}/settings/pullrequest',
843 837 controller='admin/repos', action='repo_settings_pullrequest',
844 838 conditions={'method': ['GET', 'POST'], 'function': check_repo},
845 839 requirements=URL_NAME_REQUIREMENTS)
846 840
847 841 # still working url for backward compat.
848 842 rmap.connect('raw_changeset_home_depraced',
849 843 '/{repo_name}/raw-changeset/{revision}',
850 844 controller='changeset', action='changeset_raw',
851 845 revision='tip', conditions={'function': check_repo},
852 846 requirements=URL_NAME_REQUIREMENTS)
853 847
854 848 # new URLs
855 849 rmap.connect('changeset_raw_home',
856 850 '/{repo_name}/changeset-diff/{revision}',
857 851 controller='changeset', action='changeset_raw',
858 852 revision='tip', conditions={'function': check_repo},
859 853 requirements=URL_NAME_REQUIREMENTS)
860 854
861 855 rmap.connect('changeset_patch_home',
862 856 '/{repo_name}/changeset-patch/{revision}',
863 857 controller='changeset', action='changeset_patch',
864 858 revision='tip', conditions={'function': check_repo},
865 859 requirements=URL_NAME_REQUIREMENTS)
866 860
867 861 rmap.connect('changeset_download_home',
868 862 '/{repo_name}/changeset-download/{revision}',
869 863 controller='changeset', action='changeset_download',
870 864 revision='tip', conditions={'function': check_repo},
871 865 requirements=URL_NAME_REQUIREMENTS)
872 866
873 867 rmap.connect('changeset_comment',
874 868 '/{repo_name}/changeset/{revision}/comment', jsroute=True,
875 869 controller='changeset', revision='tip', action='comment',
876 870 conditions={'function': check_repo},
877 871 requirements=URL_NAME_REQUIREMENTS)
878 872
879 873 rmap.connect('changeset_comment_preview',
880 874 '/{repo_name}/changeset/comment/preview', jsroute=True,
881 875 controller='changeset', action='preview_comment',
882 876 conditions={'function': check_repo, 'method': ['POST']},
883 877 requirements=URL_NAME_REQUIREMENTS)
884 878
885 879 rmap.connect('changeset_comment_delete',
886 880 '/{repo_name}/changeset/comment/{comment_id}/delete',
887 881 controller='changeset', action='delete_comment',
888 882 conditions={'function': check_repo, 'method': ['DELETE']},
889 883 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
890 884
891 885 rmap.connect('changeset_info', '/{repo_name}/changeset_info/{revision}',
892 886 controller='changeset', action='changeset_info',
893 887 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
894 888
895 889 rmap.connect('compare_home',
896 890 '/{repo_name}/compare',
897 891 controller='compare', action='index',
898 892 conditions={'function': check_repo},
899 893 requirements=URL_NAME_REQUIREMENTS)
900 894
901 895 rmap.connect('compare_url',
902 896 '/{repo_name}/compare/{source_ref_type}@{source_ref:.*?}...{target_ref_type}@{target_ref:.*?}',
903 897 controller='compare', action='compare',
904 898 conditions={'function': check_repo},
905 899 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
906 900
907 901 rmap.connect('pullrequest_home',
908 902 '/{repo_name}/pull-request/new', controller='pullrequests',
909 903 action='index', conditions={'function': check_repo,
910 904 'method': ['GET']},
911 905 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
912 906
913 907 rmap.connect('pullrequest',
914 908 '/{repo_name}/pull-request/new', controller='pullrequests',
915 909 action='create', conditions={'function': check_repo,
916 910 'method': ['POST']},
917 911 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
918 912
919 913 rmap.connect('pullrequest_repo_refs',
920 914 '/{repo_name}/pull-request/refs/{target_repo_name:.*?[^/]}',
921 915 controller='pullrequests',
922 916 action='get_repo_refs',
923 917 conditions={'function': check_repo, 'method': ['GET']},
924 918 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
925 919
926 920 rmap.connect('pullrequest_repo_destinations',
927 921 '/{repo_name}/pull-request/repo-destinations',
928 922 controller='pullrequests',
929 923 action='get_repo_destinations',
930 924 conditions={'function': check_repo, 'method': ['GET']},
931 925 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
932 926
933 927 rmap.connect('pullrequest_show',
934 928 '/{repo_name}/pull-request/{pull_request_id}',
935 929 controller='pullrequests',
936 930 action='show', conditions={'function': check_repo,
937 931 'method': ['GET']},
938 932 requirements=URL_NAME_REQUIREMENTS)
939 933
940 934 rmap.connect('pullrequest_update',
941 935 '/{repo_name}/pull-request/{pull_request_id}',
942 936 controller='pullrequests',
943 937 action='update', conditions={'function': check_repo,
944 938 'method': ['PUT']},
945 939 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
946 940
947 941 rmap.connect('pullrequest_merge',
948 942 '/{repo_name}/pull-request/{pull_request_id}',
949 943 controller='pullrequests',
950 944 action='merge', conditions={'function': check_repo,
951 945 'method': ['POST']},
952 946 requirements=URL_NAME_REQUIREMENTS)
953 947
954 948 rmap.connect('pullrequest_delete',
955 949 '/{repo_name}/pull-request/{pull_request_id}',
956 950 controller='pullrequests',
957 951 action='delete', conditions={'function': check_repo,
958 952 'method': ['DELETE']},
959 953 requirements=URL_NAME_REQUIREMENTS)
960 954
961 955 rmap.connect('pullrequest_show_all',
962 956 '/{repo_name}/pull-request',
963 957 controller='pullrequests',
964 958 action='show_all', conditions={'function': check_repo,
965 959 'method': ['GET']},
966 960 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
967 961
968 962 rmap.connect('pullrequest_comment',
969 963 '/{repo_name}/pull-request-comment/{pull_request_id}',
970 964 controller='pullrequests',
971 965 action='comment', conditions={'function': check_repo,
972 966 'method': ['POST']},
973 967 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
974 968
975 969 rmap.connect('pullrequest_comment_delete',
976 970 '/{repo_name}/pull-request-comment/{comment_id}/delete',
977 971 controller='pullrequests', action='delete_comment',
978 972 conditions={'function': check_repo, 'method': ['DELETE']},
979 973 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
980 974
981 975 rmap.connect('summary_home_explicit', '/{repo_name}/summary',
982 976 controller='summary', conditions={'function': check_repo},
983 977 requirements=URL_NAME_REQUIREMENTS)
984 978
985 979 rmap.connect('branches_home', '/{repo_name}/branches',
986 980 controller='branches', conditions={'function': check_repo},
987 981 requirements=URL_NAME_REQUIREMENTS)
988 982
989 983 rmap.connect('tags_home', '/{repo_name}/tags',
990 984 controller='tags', conditions={'function': check_repo},
991 985 requirements=URL_NAME_REQUIREMENTS)
992 986
993 987 rmap.connect('bookmarks_home', '/{repo_name}/bookmarks',
994 988 controller='bookmarks', conditions={'function': check_repo},
995 989 requirements=URL_NAME_REQUIREMENTS)
996 990
997 991 rmap.connect('changelog_home', '/{repo_name}/changelog', jsroute=True,
998 992 controller='changelog', conditions={'function': check_repo},
999 993 requirements=URL_NAME_REQUIREMENTS)
1000 994
1001 995 rmap.connect('changelog_summary_home', '/{repo_name}/changelog_summary',
1002 996 controller='changelog', action='changelog_summary',
1003 997 conditions={'function': check_repo},
1004 998 requirements=URL_NAME_REQUIREMENTS)
1005 999
1006 1000 rmap.connect('changelog_file_home',
1007 1001 '/{repo_name}/changelog/{revision}/{f_path}',
1008 1002 controller='changelog', f_path=None,
1009 1003 conditions={'function': check_repo},
1010 1004 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1011 1005
1012 1006 rmap.connect('changelog_details', '/{repo_name}/changelog_details/{cs}',
1013 1007 controller='changelog', action='changelog_details',
1014 1008 conditions={'function': check_repo},
1015 1009 requirements=URL_NAME_REQUIREMENTS)
1016 1010
1017 1011 rmap.connect('files_home', '/{repo_name}/files/{revision}/{f_path}',
1018 1012 controller='files', revision='tip', f_path='',
1019 1013 conditions={'function': check_repo},
1020 1014 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1021 1015
1022 1016 rmap.connect('files_home_simple_catchrev',
1023 1017 '/{repo_name}/files/{revision}',
1024 1018 controller='files', revision='tip', f_path='',
1025 1019 conditions={'function': check_repo},
1026 1020 requirements=URL_NAME_REQUIREMENTS)
1027 1021
1028 1022 rmap.connect('files_home_simple_catchall',
1029 1023 '/{repo_name}/files',
1030 1024 controller='files', revision='tip', f_path='',
1031 1025 conditions={'function': check_repo},
1032 1026 requirements=URL_NAME_REQUIREMENTS)
1033 1027
1034 1028 rmap.connect('files_history_home',
1035 1029 '/{repo_name}/history/{revision}/{f_path}',
1036 1030 controller='files', action='history', revision='tip', f_path='',
1037 1031 conditions={'function': check_repo},
1038 1032 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1039 1033
1040 1034 rmap.connect('files_authors_home',
1041 1035 '/{repo_name}/authors/{revision}/{f_path}',
1042 1036 controller='files', action='authors', revision='tip', f_path='',
1043 1037 conditions={'function': check_repo},
1044 1038 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1045 1039
1046 1040 rmap.connect('files_diff_home', '/{repo_name}/diff/{f_path}',
1047 1041 controller='files', action='diff', f_path='',
1048 1042 conditions={'function': check_repo},
1049 1043 requirements=URL_NAME_REQUIREMENTS)
1050 1044
1051 1045 rmap.connect('files_diff_2way_home',
1052 1046 '/{repo_name}/diff-2way/{f_path}',
1053 1047 controller='files', action='diff_2way', f_path='',
1054 1048 conditions={'function': check_repo},
1055 1049 requirements=URL_NAME_REQUIREMENTS)
1056 1050
1057 1051 rmap.connect('files_rawfile_home',
1058 1052 '/{repo_name}/rawfile/{revision}/{f_path}',
1059 1053 controller='files', action='rawfile', revision='tip',
1060 1054 f_path='', conditions={'function': check_repo},
1061 1055 requirements=URL_NAME_REQUIREMENTS)
1062 1056
1063 1057 rmap.connect('files_raw_home',
1064 1058 '/{repo_name}/raw/{revision}/{f_path}',
1065 1059 controller='files', action='raw', revision='tip', f_path='',
1066 1060 conditions={'function': check_repo},
1067 1061 requirements=URL_NAME_REQUIREMENTS)
1068 1062
1069 1063 rmap.connect('files_render_home',
1070 1064 '/{repo_name}/render/{revision}/{f_path}',
1071 1065 controller='files', action='index', revision='tip', f_path='',
1072 1066 rendered=True, conditions={'function': check_repo},
1073 1067 requirements=URL_NAME_REQUIREMENTS)
1074 1068
1075 1069 rmap.connect('files_annotate_home',
1076 1070 '/{repo_name}/annotate/{revision}/{f_path}',
1077 1071 controller='files', action='index', revision='tip',
1078 1072 f_path='', annotate=True, conditions={'function': check_repo},
1079 1073 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1080 1074
1081 1075 rmap.connect('files_edit',
1082 1076 '/{repo_name}/edit/{revision}/{f_path}',
1083 1077 controller='files', action='edit', revision='tip',
1084 1078 f_path='',
1085 1079 conditions={'function': check_repo, 'method': ['POST']},
1086 1080 requirements=URL_NAME_REQUIREMENTS)
1087 1081
1088 1082 rmap.connect('files_edit_home',
1089 1083 '/{repo_name}/edit/{revision}/{f_path}',
1090 1084 controller='files', action='edit_home', revision='tip',
1091 1085 f_path='', conditions={'function': check_repo},
1092 1086 requirements=URL_NAME_REQUIREMENTS)
1093 1087
1094 1088 rmap.connect('files_add',
1095 1089 '/{repo_name}/add/{revision}/{f_path}',
1096 1090 controller='files', action='add', revision='tip',
1097 1091 f_path='',
1098 1092 conditions={'function': check_repo, 'method': ['POST']},
1099 1093 requirements=URL_NAME_REQUIREMENTS)
1100 1094
1101 1095 rmap.connect('files_add_home',
1102 1096 '/{repo_name}/add/{revision}/{f_path}',
1103 1097 controller='files', action='add_home', revision='tip',
1104 1098 f_path='', conditions={'function': check_repo},
1105 1099 requirements=URL_NAME_REQUIREMENTS)
1106 1100
1107 1101 rmap.connect('files_delete',
1108 1102 '/{repo_name}/delete/{revision}/{f_path}',
1109 1103 controller='files', action='delete', revision='tip',
1110 1104 f_path='',
1111 1105 conditions={'function': check_repo, 'method': ['POST']},
1112 1106 requirements=URL_NAME_REQUIREMENTS)
1113 1107
1114 1108 rmap.connect('files_delete_home',
1115 1109 '/{repo_name}/delete/{revision}/{f_path}',
1116 1110 controller='files', action='delete_home', revision='tip',
1117 1111 f_path='', conditions={'function': check_repo},
1118 1112 requirements=URL_NAME_REQUIREMENTS)
1119 1113
1120 1114 rmap.connect('files_archive_home', '/{repo_name}/archive/{fname}',
1121 1115 controller='files', action='archivefile',
1122 1116 conditions={'function': check_repo},
1123 1117 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1124 1118
1125 1119 rmap.connect('files_nodelist_home',
1126 1120 '/{repo_name}/nodelist/{revision}/{f_path}',
1127 1121 controller='files', action='nodelist',
1128 1122 conditions={'function': check_repo},
1129 1123 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1130 1124
1131 1125 rmap.connect('files_nodetree_full',
1132 1126 '/{repo_name}/nodetree_full/{commit_id}/{f_path}',
1133 1127 controller='files', action='nodetree_full',
1134 1128 conditions={'function': check_repo},
1135 1129 requirements=URL_NAME_REQUIREMENTS, jsroute=True)
1136 1130
1137 1131 rmap.connect('repo_fork_create_home', '/{repo_name}/fork',
1138 1132 controller='forks', action='fork_create',
1139 1133 conditions={'function': check_repo, 'method': ['POST']},
1140 1134 requirements=URL_NAME_REQUIREMENTS)
1141 1135
1142 1136 rmap.connect('repo_fork_home', '/{repo_name}/fork',
1143 1137 controller='forks', action='fork',
1144 1138 conditions={'function': check_repo},
1145 1139 requirements=URL_NAME_REQUIREMENTS)
1146 1140
1147 1141 rmap.connect('repo_forks_home', '/{repo_name}/forks',
1148 1142 controller='forks', action='forks',
1149 1143 conditions={'function': check_repo},
1150 1144 requirements=URL_NAME_REQUIREMENTS)
1151 1145
1152 1146 rmap.connect('repo_followers_home', '/{repo_name}/followers',
1153 1147 controller='followers', action='followers',
1154 1148 conditions={'function': check_repo},
1155 1149 requirements=URL_NAME_REQUIREMENTS)
1156 1150
1157 1151 # must be here for proper group/repo catching pattern
1158 1152 _connect_with_slash(
1159 1153 rmap, 'repo_group_home', '/{group_name}',
1160 1154 controller='home', action='index_repo_group',
1161 1155 conditions={'function': check_group},
1162 1156 requirements=URL_NAME_REQUIREMENTS)
1163 1157
1164 1158 # catch all, at the end
1165 1159 _connect_with_slash(
1166 1160 rmap, 'summary_home', '/{repo_name}', jsroute=True,
1167 1161 controller='summary', action='index',
1168 1162 conditions={'function': check_repo},
1169 1163 requirements=URL_NAME_REQUIREMENTS)
1170 1164
1171 1165 return rmap
1172 1166
1173 1167
1174 1168 def _connect_with_slash(mapper, name, path, *args, **kwargs):
1175 1169 """
1176 1170 Connect a route with an optional trailing slash in `path`.
1177 1171 """
1178 1172 mapper.connect(name + '_slash', path + '/', *args, **kwargs)
1179 1173 mapper.connect(name, path, *args, **kwargs)
@@ -1,890 +1,842 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 pyramid.threadlocal import get_current_registry
38 38 from webob.exc import HTTPBadRequest
39 39
40 40 import rhodecode
41 41 from rhodecode.admin.navigation import navigation_list
42 42 from rhodecode.lib import auth
43 43 from rhodecode.lib import helpers as h
44 44 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
45 45 from rhodecode.lib.base import BaseController, render
46 46 from rhodecode.lib.celerylib import tasks, run_task
47 47 from rhodecode.lib.utils import repo2db_mapper
48 48 from rhodecode.lib.utils2 import (
49 49 str2bool, safe_unicode, AttributeDict, safe_int)
50 50 from rhodecode.lib.compat import OrderedDict
51 51 from rhodecode.lib.ext_json import json
52 52 from rhodecode.lib.utils import jsonify
53 from rhodecode.lib import system_info
54 from rhodecode.lib import user_sessions
55 53
56 54 from rhodecode.model.db import RhodeCodeUi, Repository
57 55 from rhodecode.model.forms import ApplicationSettingsForm, \
58 56 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
59 57 LabsSettingsForm, IssueTrackerPatternsForm
60 58 from rhodecode.model.repo_group import RepoGroupModel
61 59
62 60 from rhodecode.model.scm import ScmModel
63 61 from rhodecode.model.notification import EmailNotificationModel
64 62 from rhodecode.model.meta import Session
65 63 from rhodecode.model.settings import (
66 64 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
67 65 SettingsModel)
68 66
69 67 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
70 68 from rhodecode.svn_support.config_keys import generate_config
71 69
72 70
73 71 log = logging.getLogger(__name__)
74 72
75 73
76 74 class SettingsController(BaseController):
77 75 """REST Controller styled on the Atom Publishing Protocol"""
78 76 # To properly map this controller, ensure your config/routing.py
79 77 # file has a resource setup:
80 78 # map.resource('setting', 'settings', controller='admin/settings',
81 79 # path_prefix='/admin', name_prefix='admin_')
82 80
83 81 @LoginRequired()
84 82 def __before__(self):
85 83 super(SettingsController, self).__before__()
86 84 c.labs_active = str2bool(
87 85 rhodecode.CONFIG.get('labs_settings_active', 'true'))
88 86 c.navlist = navigation_list(request)
89 87
90 88 def _get_hg_ui_settings(self):
91 89 ret = RhodeCodeUi.query().all()
92 90
93 91 if not ret:
94 92 raise Exception('Could not get application ui settings !')
95 93 settings = {}
96 94 for each in ret:
97 95 k = each.ui_key
98 96 v = each.ui_value
99 97 if k == '/':
100 98 k = 'root_path'
101 99
102 100 if k in ['push_ssl', 'publish']:
103 101 v = str2bool(v)
104 102
105 103 if k.find('.') != -1:
106 104 k = k.replace('.', '_')
107 105
108 106 if each.ui_section in ['hooks', 'extensions']:
109 107 v = each.ui_active
110 108
111 109 settings[each.ui_section + '_' + k] = v
112 110 return settings
113 111
114 112 @HasPermissionAllDecorator('hg.admin')
115 113 @auth.CSRFRequired()
116 114 @jsonify
117 115 def delete_svn_pattern(self):
118 116 if not request.is_xhr:
119 117 raise HTTPBadRequest()
120 118
121 119 delete_pattern_id = request.POST.get('delete_svn_pattern')
122 120 model = VcsSettingsModel()
123 121 try:
124 122 model.delete_global_svn_pattern(delete_pattern_id)
125 123 except SettingNotFound:
126 124 raise HTTPBadRequest()
127 125
128 126 Session().commit()
129 127 return True
130 128
131 129 @HasPermissionAllDecorator('hg.admin')
132 130 @auth.CSRFRequired()
133 131 def settings_vcs_update(self):
134 132 """POST /admin/settings: All items in the collection"""
135 133 # url('admin_settings_vcs')
136 134 c.active = 'vcs'
137 135
138 136 model = VcsSettingsModel()
139 137 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
140 138 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
141 139
142 140 # TODO: Replace with request.registry after migrating to pyramid.
143 141 pyramid_settings = get_current_registry().settings
144 142 c.svn_proxy_generate_config = pyramid_settings[generate_config]
145 143
146 144 application_form = ApplicationUiSettingsForm()()
147 145
148 146 try:
149 147 form_result = application_form.to_python(dict(request.POST))
150 148 except formencode.Invalid as errors:
151 149 h.flash(
152 150 _("Some form inputs contain invalid data."),
153 151 category='error')
154 152 return htmlfill.render(
155 153 render('admin/settings/settings.mako'),
156 154 defaults=errors.value,
157 155 errors=errors.error_dict or {},
158 156 prefix_error=False,
159 157 encoding="UTF-8",
160 158 force_defaults=False
161 159 )
162 160
163 161 try:
164 162 if c.visual.allow_repo_location_change:
165 163 model.update_global_path_setting(
166 164 form_result['paths_root_path'])
167 165
168 166 model.update_global_ssl_setting(form_result['web_push_ssl'])
169 167 model.update_global_hook_settings(form_result)
170 168
171 169 model.create_or_update_global_svn_settings(form_result)
172 170 model.create_or_update_global_hg_settings(form_result)
173 171 model.create_or_update_global_pr_settings(form_result)
174 172 except Exception:
175 173 log.exception("Exception while updating settings")
176 174 h.flash(_('Error occurred during updating '
177 175 'application settings'), category='error')
178 176 else:
179 177 Session().commit()
180 178 h.flash(_('Updated VCS settings'), category='success')
181 179 return redirect(url('admin_settings_vcs'))
182 180
183 181 return htmlfill.render(
184 182 render('admin/settings/settings.mako'),
185 183 defaults=self._form_defaults(),
186 184 encoding="UTF-8",
187 185 force_defaults=False)
188 186
189 187 @HasPermissionAllDecorator('hg.admin')
190 188 def settings_vcs(self):
191 189 """GET /admin/settings: All items in the collection"""
192 190 # url('admin_settings_vcs')
193 191 c.active = 'vcs'
194 192 model = VcsSettingsModel()
195 193 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
196 194 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
197 195
198 196 # TODO: Replace with request.registry after migrating to pyramid.
199 197 pyramid_settings = get_current_registry().settings
200 198 c.svn_proxy_generate_config = pyramid_settings[generate_config]
201 199
202 200 return htmlfill.render(
203 201 render('admin/settings/settings.mako'),
204 202 defaults=self._form_defaults(),
205 203 encoding="UTF-8",
206 204 force_defaults=False)
207 205
208 206 @HasPermissionAllDecorator('hg.admin')
209 207 @auth.CSRFRequired()
210 208 def settings_mapping_update(self):
211 209 """POST /admin/settings/mapping: All items in the collection"""
212 210 # url('admin_settings_mapping')
213 211 c.active = 'mapping'
214 212 rm_obsolete = request.POST.get('destroy', False)
215 213 invalidate_cache = request.POST.get('invalidate', False)
216 214 log.debug(
217 215 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
218 216
219 217 if invalidate_cache:
220 218 log.debug('invalidating all repositories cache')
221 219 for repo in Repository.get_all():
222 220 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
223 221
224 222 filesystem_repos = ScmModel().repo_scan()
225 223 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
226 224 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
227 225 h.flash(_('Repositories successfully '
228 226 'rescanned added: %s ; removed: %s') %
229 227 (_repr(added), _repr(removed)),
230 228 category='success')
231 229 return redirect(url('admin_settings_mapping'))
232 230
233 231 @HasPermissionAllDecorator('hg.admin')
234 232 def settings_mapping(self):
235 233 """GET /admin/settings/mapping: All items in the collection"""
236 234 # url('admin_settings_mapping')
237 235 c.active = 'mapping'
238 236
239 237 return htmlfill.render(
240 238 render('admin/settings/settings.mako'),
241 239 defaults=self._form_defaults(),
242 240 encoding="UTF-8",
243 241 force_defaults=False)
244 242
245 243 @HasPermissionAllDecorator('hg.admin')
246 244 @auth.CSRFRequired()
247 245 def settings_global_update(self):
248 246 """POST /admin/settings/global: All items in the collection"""
249 247 # url('admin_settings_global')
250 248 c.active = 'global'
251 249 c.personal_repo_group_default_pattern = RepoGroupModel()\
252 250 .get_personal_group_name_pattern()
253 251 application_form = ApplicationSettingsForm()()
254 252 try:
255 253 form_result = application_form.to_python(dict(request.POST))
256 254 except formencode.Invalid as errors:
257 255 return htmlfill.render(
258 256 render('admin/settings/settings.mako'),
259 257 defaults=errors.value,
260 258 errors=errors.error_dict or {},
261 259 prefix_error=False,
262 260 encoding="UTF-8",
263 261 force_defaults=False)
264 262
265 263 try:
266 264 settings = [
267 265 ('title', 'rhodecode_title', 'unicode'),
268 266 ('realm', 'rhodecode_realm', 'unicode'),
269 267 ('pre_code', 'rhodecode_pre_code', 'unicode'),
270 268 ('post_code', 'rhodecode_post_code', 'unicode'),
271 269 ('captcha_public_key', 'rhodecode_captcha_public_key', 'unicode'),
272 270 ('captcha_private_key', 'rhodecode_captcha_private_key', 'unicode'),
273 271 ('create_personal_repo_group', 'rhodecode_create_personal_repo_group', 'bool'),
274 272 ('personal_repo_group_pattern', 'rhodecode_personal_repo_group_pattern', 'unicode'),
275 273 ]
276 274 for setting, form_key, type_ in settings:
277 275 sett = SettingsModel().create_or_update_setting(
278 276 setting, form_result[form_key], type_)
279 277 Session().add(sett)
280 278
281 279 Session().commit()
282 280 SettingsModel().invalidate_settings_cache()
283 281 h.flash(_('Updated application settings'), category='success')
284 282 except Exception:
285 283 log.exception("Exception while updating application settings")
286 284 h.flash(
287 285 _('Error occurred during updating application settings'),
288 286 category='error')
289 287
290 288 return redirect(url('admin_settings_global'))
291 289
292 290 @HasPermissionAllDecorator('hg.admin')
293 291 def settings_global(self):
294 292 """GET /admin/settings/global: All items in the collection"""
295 293 # url('admin_settings_global')
296 294 c.active = 'global'
297 295 c.personal_repo_group_default_pattern = RepoGroupModel()\
298 296 .get_personal_group_name_pattern()
299 297
300 298 return htmlfill.render(
301 299 render('admin/settings/settings.mako'),
302 300 defaults=self._form_defaults(),
303 301 encoding="UTF-8",
304 302 force_defaults=False)
305 303
306 304 @HasPermissionAllDecorator('hg.admin')
307 305 @auth.CSRFRequired()
308 306 def settings_visual_update(self):
309 307 """POST /admin/settings/visual: All items in the collection"""
310 308 # url('admin_settings_visual')
311 309 c.active = 'visual'
312 310 application_form = ApplicationVisualisationForm()()
313 311 try:
314 312 form_result = application_form.to_python(dict(request.POST))
315 313 except formencode.Invalid as errors:
316 314 return htmlfill.render(
317 315 render('admin/settings/settings.mako'),
318 316 defaults=errors.value,
319 317 errors=errors.error_dict or {},
320 318 prefix_error=False,
321 319 encoding="UTF-8",
322 320 force_defaults=False
323 321 )
324 322
325 323 try:
326 324 settings = [
327 325 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
328 326 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
329 327 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
330 328 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
331 329 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
332 330 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
333 331 ('show_version', 'rhodecode_show_version', 'bool'),
334 332 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
335 333 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
336 334 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
337 335 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
338 336 ('support_url', 'rhodecode_support_url', 'unicode'),
339 337 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
340 338 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
341 339 ]
342 340 for setting, form_key, type_ in settings:
343 341 sett = SettingsModel().create_or_update_setting(
344 342 setting, form_result[form_key], type_)
345 343 Session().add(sett)
346 344
347 345 Session().commit()
348 346 SettingsModel().invalidate_settings_cache()
349 347 h.flash(_('Updated visualisation settings'), category='success')
350 348 except Exception:
351 349 log.exception("Exception updating visualization settings")
352 350 h.flash(_('Error occurred during updating '
353 351 'visualisation settings'),
354 352 category='error')
355 353
356 354 return redirect(url('admin_settings_visual'))
357 355
358 356 @HasPermissionAllDecorator('hg.admin')
359 357 def settings_visual(self):
360 358 """GET /admin/settings/visual: All items in the collection"""
361 359 # url('admin_settings_visual')
362 360 c.active = 'visual'
363 361
364 362 return htmlfill.render(
365 363 render('admin/settings/settings.mako'),
366 364 defaults=self._form_defaults(),
367 365 encoding="UTF-8",
368 366 force_defaults=False)
369 367
370 368 @HasPermissionAllDecorator('hg.admin')
371 369 @auth.CSRFRequired()
372 370 def settings_issuetracker_test(self):
373 371 if request.is_xhr:
374 372 return h.urlify_commit_message(
375 373 request.POST.get('test_text', ''),
376 374 'repo_group/test_repo1')
377 375 else:
378 376 raise HTTPBadRequest()
379 377
380 378 @HasPermissionAllDecorator('hg.admin')
381 379 @auth.CSRFRequired()
382 380 def settings_issuetracker_delete(self):
383 381 uid = request.POST.get('uid')
384 382 IssueTrackerSettingsModel().delete_entries(uid)
385 383 h.flash(_('Removed issue tracker entry'), category='success')
386 384 return redirect(url('admin_settings_issuetracker'))
387 385
388 386 @HasPermissionAllDecorator('hg.admin')
389 387 def settings_issuetracker(self):
390 388 """GET /admin/settings/issue-tracker: All items in the collection"""
391 389 # url('admin_settings_issuetracker')
392 390 c.active = 'issuetracker'
393 391 defaults = SettingsModel().get_all_settings()
394 392
395 393 entry_key = 'rhodecode_issuetracker_pat_'
396 394
397 395 c.issuetracker_entries = {}
398 396 for k, v in defaults.items():
399 397 if k.startswith(entry_key):
400 398 uid = k[len(entry_key):]
401 399 c.issuetracker_entries[uid] = None
402 400
403 401 for uid in c.issuetracker_entries:
404 402 c.issuetracker_entries[uid] = AttributeDict({
405 403 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
406 404 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
407 405 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
408 406 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
409 407 })
410 408
411 409 return render('admin/settings/settings.mako')
412 410
413 411 @HasPermissionAllDecorator('hg.admin')
414 412 @auth.CSRFRequired()
415 413 def settings_issuetracker_save(self):
416 414 settings_model = IssueTrackerSettingsModel()
417 415
418 416 form = IssueTrackerPatternsForm()().to_python(request.POST)
419 417 if form:
420 418 for uid in form.get('delete_patterns', []):
421 419 settings_model.delete_entries(uid)
422 420
423 421 for pattern in form.get('patterns', []):
424 422 for setting, value, type_ in pattern:
425 423 sett = settings_model.create_or_update_setting(
426 424 setting, value, type_)
427 425 Session().add(sett)
428 426
429 427 Session().commit()
430 428
431 429 SettingsModel().invalidate_settings_cache()
432 430 h.flash(_('Updated issue tracker entries'), category='success')
433 431 return redirect(url('admin_settings_issuetracker'))
434 432
435 433 @HasPermissionAllDecorator('hg.admin')
436 434 @auth.CSRFRequired()
437 435 def settings_email_update(self):
438 436 """POST /admin/settings/email: All items in the collection"""
439 437 # url('admin_settings_email')
440 438 c.active = 'email'
441 439
442 440 test_email = request.POST.get('test_email')
443 441
444 442 if not test_email:
445 443 h.flash(_('Please enter email address'), category='error')
446 444 return redirect(url('admin_settings_email'))
447 445
448 446 email_kwargs = {
449 447 'date': datetime.datetime.now(),
450 448 'user': c.rhodecode_user,
451 449 'rhodecode_version': c.rhodecode_version
452 450 }
453 451
454 452 (subject, headers, email_body,
455 453 email_body_plaintext) = EmailNotificationModel().render_email(
456 454 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
457 455
458 456 recipients = [test_email] if test_email else None
459 457
460 458 run_task(tasks.send_email, recipients, subject,
461 459 email_body_plaintext, email_body)
462 460
463 461 h.flash(_('Send email task created'), category='success')
464 462 return redirect(url('admin_settings_email'))
465 463
466 464 @HasPermissionAllDecorator('hg.admin')
467 465 def settings_email(self):
468 466 """GET /admin/settings/email: All items in the collection"""
469 467 # url('admin_settings_email')
470 468 c.active = 'email'
471 469 c.rhodecode_ini = rhodecode.CONFIG
472 470
473 471 return htmlfill.render(
474 472 render('admin/settings/settings.mako'),
475 473 defaults=self._form_defaults(),
476 474 encoding="UTF-8",
477 475 force_defaults=False)
478 476
479 477 @HasPermissionAllDecorator('hg.admin')
480 478 @auth.CSRFRequired()
481 479 def settings_hooks_update(self):
482 480 """POST or DELETE /admin/settings/hooks: All items in the collection"""
483 481 # url('admin_settings_hooks')
484 482 c.active = 'hooks'
485 483 if c.visual.allow_custom_hooks_settings:
486 484 ui_key = request.POST.get('new_hook_ui_key')
487 485 ui_value = request.POST.get('new_hook_ui_value')
488 486
489 487 hook_id = request.POST.get('hook_id')
490 488 new_hook = False
491 489
492 490 model = SettingsModel()
493 491 try:
494 492 if ui_value and ui_key:
495 493 model.create_or_update_hook(ui_key, ui_value)
496 494 h.flash(_('Added new hook'), category='success')
497 495 new_hook = True
498 496 elif hook_id:
499 497 RhodeCodeUi.delete(hook_id)
500 498 Session().commit()
501 499
502 500 # check for edits
503 501 update = False
504 502 _d = request.POST.dict_of_lists()
505 503 for k, v in zip(_d.get('hook_ui_key', []),
506 504 _d.get('hook_ui_value_new', [])):
507 505 model.create_or_update_hook(k, v)
508 506 update = True
509 507
510 508 if update and not new_hook:
511 509 h.flash(_('Updated hooks'), category='success')
512 510 Session().commit()
513 511 except Exception:
514 512 log.exception("Exception during hook creation")
515 513 h.flash(_('Error occurred during hook creation'),
516 514 category='error')
517 515
518 516 return redirect(url('admin_settings_hooks'))
519 517
520 518 @HasPermissionAllDecorator('hg.admin')
521 519 def settings_hooks(self):
522 520 """GET /admin/settings/hooks: All items in the collection"""
523 521 # url('admin_settings_hooks')
524 522 c.active = 'hooks'
525 523
526 524 model = SettingsModel()
527 525 c.hooks = model.get_builtin_hooks()
528 526 c.custom_hooks = model.get_custom_hooks()
529 527
530 528 return htmlfill.render(
531 529 render('admin/settings/settings.mako'),
532 530 defaults=self._form_defaults(),
533 531 encoding="UTF-8",
534 532 force_defaults=False)
535 533
536 534 @HasPermissionAllDecorator('hg.admin')
537 535 def settings_search(self):
538 536 """GET /admin/settings/search: All items in the collection"""
539 537 # url('admin_settings_search')
540 538 c.active = 'search'
541 539
542 540 from rhodecode.lib.index import searcher_from_config
543 541 searcher = searcher_from_config(config)
544 542 c.statistics = searcher.statistics()
545 543
546 544 return render('admin/settings/settings.mako')
547 545
548 546 @HasPermissionAllDecorator('hg.admin')
549 547 def settings_system(self):
550 548 """GET /admin/settings/system: All items in the collection"""
551 549 # url('admin_settings_system')
552 550 snapshot = str2bool(request.GET.get('snapshot'))
553 551 defaults = self._form_defaults()
554 552
555 553 c.active = 'system'
556 554 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
557 555 server_info = ScmModel().get_server_info(request.environ)
558 556
559 557 for key, val in server_info.iteritems():
560 558 setattr(c, key, val)
561 559
562 560 def val(name, subkey='human_value'):
563 561 return server_info[name][subkey]
564 562
565 563 def state(name):
566 564 return server_info[name]['state']
567 565
568 566 def val2(name):
569 567 val = server_info[name]['human_value']
570 568 state = server_info[name]['state']
571 569 return val, state
572 570
573 571 c.data_items = [
574 572 # update info
575 573 (_('Update info'), h.literal(
576 574 '<span class="link" id="check_for_update" >%s.</span>' % (
577 575 _('Check for updates')) +
578 576 '<br/> <span >%s.</span>' % (_('Note: please make sure this server can access `%s` for the update link to work') % c.rhodecode_update_url)
579 577 ), ''),
580 578
581 579 # RhodeCode specific
582 580 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
583 581 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
584 582 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
585 583 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
586 584 ('', '', ''), # spacer
587 585
588 586 # Database
589 587 (_('Database'), val('database')['url'], state('database')),
590 588 (_('Database version'), val('database')['version'], state('database')),
591 589 ('', '', ''), # spacer
592 590
593 591 # Platform/Python
594 592 (_('Platform'), val('platform')['name'], state('platform')),
595 593 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
596 594 (_('Python version'), val('python')['version'], state('python')),
597 595 (_('Python path'), val('python')['executable'], state('python')),
598 596 ('', '', ''), # spacer
599 597
600 598 # Systems stats
601 599 (_('CPU'), val('cpu'), state('cpu')),
602 600 (_('Load'), val('load')['text'], state('load')),
603 601 (_('Memory'), val('memory')['text'], state('memory')),
604 602 (_('Uptime'), val('uptime')['text'], state('uptime')),
605 603 ('', '', ''), # spacer
606 604
607 605 # Repo storage
608 606 (_('Storage location'), val('storage')['path'], state('storage')),
609 607 (_('Storage info'), val('storage')['text'], state('storage')),
610 608 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
611 609
612 610 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
613 611 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
614 612
615 613 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
616 614 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
617 615
618 616 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
619 617 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
620 618
621 619 (_('Search info'), val('search')['text'], state('search')),
622 620 (_('Search location'), val('search')['location'], state('search')),
623 621 ('', '', ''), # spacer
624 622
625 623 # VCS specific
626 624 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
627 625 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
628 626 (_('GIT'), val('git'), state('git')),
629 627 (_('HG'), val('hg'), state('hg')),
630 628 (_('SVN'), val('svn'), state('svn')),
631 629
632 630 ]
633 631
634 632 # TODO: marcink, figure out how to allow only selected users to do this
635 633 c.allowed_to_snapshot = c.rhodecode_user.admin
636 634
637 635 if snapshot:
638 636 if c.allowed_to_snapshot:
639 637 c.data_items.pop(0) # remove server info
640 638 return render('admin/settings/settings_system_snapshot.mako')
641 639 else:
642 640 h.flash('You are not allowed to do this', category='warning')
643 641
644 642 return htmlfill.render(
645 643 render('admin/settings/settings.mako'),
646 644 defaults=defaults,
647 645 encoding="UTF-8",
648 646 force_defaults=False)
649 647
650 648 @staticmethod
651 649 def get_update_data(update_url):
652 650 """Return the JSON update data."""
653 651 ver = rhodecode.__version__
654 652 log.debug('Checking for upgrade on `%s` server', update_url)
655 653 opener = urllib2.build_opener()
656 654 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
657 655 response = opener.open(update_url)
658 656 response_data = response.read()
659 657 data = json.loads(response_data)
660 658
661 659 return data
662 660
663 661 @HasPermissionAllDecorator('hg.admin')
664 662 def settings_system_update(self):
665 663 """GET /admin/settings/system/updates: All items in the collection"""
666 664 # url('admin_settings_system_update')
667 665 defaults = self._form_defaults()
668 666 update_url = defaults.get('rhodecode_update_url', '')
669 667
670 668 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
671 669 try:
672 670 data = self.get_update_data(update_url)
673 671 except urllib2.URLError as e:
674 672 log.exception("Exception contacting upgrade server")
675 673 return _err('Failed to contact upgrade server: %r' % e)
676 674 except ValueError as e:
677 675 log.exception("Bad data sent from update server")
678 676 return _err('Bad data sent from update server')
679 677
680 678 latest = data['versions'][0]
681 679
682 680 c.update_url = update_url
683 681 c.latest_data = latest
684 682 c.latest_ver = latest['version']
685 683 c.cur_ver = rhodecode.__version__
686 684 c.should_upgrade = False
687 685
688 686 if (packaging.version.Version(c.latest_ver) >
689 687 packaging.version.Version(c.cur_ver)):
690 688 c.should_upgrade = True
691 689 c.important_notices = latest['general']
692 690
693 691 return render('admin/settings/settings_system_update.mako')
694 692
695 693 @HasPermissionAllDecorator('hg.admin')
696 def settings_sessions(self):
697 # url('admin_settings_sessions')
698
699 c.active = 'sessions'
700 c.cleanup_older_days = 60
701 older_than_seconds = 24 * 60 * 60 * 24 * c.cleanup_older_days
702
703 config = system_info.rhodecode_config().get_value()['value']['config']
704 c.session_model = user_sessions.get_session_handler(
705 config.get('beaker.session.type', 'memory'))(config)
706
707 c.session_conf = c.session_model.config
708 c.session_count = c.session_model.get_count()
709 c.session_expired_count = c.session_model.get_expired_count(
710 older_than_seconds)
711
712 return render('admin/settings/settings.mako')
713
714 @HasPermissionAllDecorator('hg.admin')
715 def settings_sessions_cleanup(self):
716 # url('admin_settings_sessions_update')
717
718 expire_days = safe_int(request.POST.get('expire_days'))
719
720 if expire_days is None:
721 expire_days = 60
722
723 older_than_seconds = 24 * 60 * 60 * 24 * expire_days
724
725 config = system_info.rhodecode_config().get_value()['value']['config']
726 session_model = user_sessions.get_session_handler(
727 config.get('beaker.session.type', 'memory'))(config)
728
729 try:
730 session_model.clean_sessions(
731 older_than_seconds=older_than_seconds)
732 h.flash(_('Cleaned up old sessions'), category='success')
733 except user_sessions.CleanupCommand as msg:
734 h.flash(msg, category='warning')
735 except Exception as e:
736 log.exception('Failed session cleanup')
737 h.flash(_('Failed to cleanup up old sessions'), category='error')
738
739 return redirect(url('admin_settings_sessions'))
740
741 @HasPermissionAllDecorator('hg.admin')
742 694 def settings_supervisor(self):
743 695 c.rhodecode_ini = rhodecode.CONFIG
744 696 c.active = 'supervisor'
745 697
746 698 c.supervisor_procs = OrderedDict([
747 699 (SUPERVISOR_MASTER, {}),
748 700 ])
749 701
750 702 c.log_size = 10240
751 703 supervisor = SupervisorModel()
752 704
753 705 _connection = supervisor.get_connection(
754 706 c.rhodecode_ini.get('supervisor.uri'))
755 707 c.connection_error = None
756 708 try:
757 709 _connection.supervisor.getAllProcessInfo()
758 710 except Exception as e:
759 711 c.connection_error = str(e)
760 712 log.exception("Exception reading supervisor data")
761 713 return render('admin/settings/settings.mako')
762 714
763 715 groupid = c.rhodecode_ini.get('supervisor.group_id')
764 716
765 717 # feed our group processes to the main
766 718 for proc in supervisor.get_group_processes(_connection, groupid):
767 719 c.supervisor_procs[proc['name']] = {}
768 720
769 721 for k in c.supervisor_procs.keys():
770 722 try:
771 723 # master process info
772 724 if k == SUPERVISOR_MASTER:
773 725 _data = supervisor.get_master_state(_connection)
774 726 _data['name'] = 'supervisor master'
775 727 _data['description'] = 'pid %s, id: %s, ver: %s' % (
776 728 _data['pid'], _data['id'], _data['ver'])
777 729 c.supervisor_procs[k] = _data
778 730 else:
779 731 procid = groupid + ":" + k
780 732 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
781 733 except Exception as e:
782 734 log.exception("Exception reading supervisor data")
783 735 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
784 736
785 737 return render('admin/settings/settings.mako')
786 738
787 739 @HasPermissionAllDecorator('hg.admin')
788 740 def settings_supervisor_log(self, procid):
789 741 import rhodecode
790 742 c.rhodecode_ini = rhodecode.CONFIG
791 743 c.active = 'supervisor_tail'
792 744
793 745 supervisor = SupervisorModel()
794 746 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
795 747 groupid = c.rhodecode_ini.get('supervisor.group_id')
796 748 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
797 749
798 750 c.log_size = 10240
799 751 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
800 752 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
801 753
802 754 return render('admin/settings/settings.mako')
803 755
804 756 @HasPermissionAllDecorator('hg.admin')
805 757 @auth.CSRFRequired()
806 758 def settings_labs_update(self):
807 759 """POST /admin/settings/labs: All items in the collection"""
808 760 # url('admin_settings/labs', method={'POST'})
809 761 c.active = 'labs'
810 762
811 763 application_form = LabsSettingsForm()()
812 764 try:
813 765 form_result = application_form.to_python(dict(request.POST))
814 766 except formencode.Invalid as errors:
815 767 h.flash(
816 768 _('Some form inputs contain invalid data.'),
817 769 category='error')
818 770 return htmlfill.render(
819 771 render('admin/settings/settings.mako'),
820 772 defaults=errors.value,
821 773 errors=errors.error_dict or {},
822 774 prefix_error=False,
823 775 encoding='UTF-8',
824 776 force_defaults=False
825 777 )
826 778
827 779 try:
828 780 session = Session()
829 781 for setting in _LAB_SETTINGS:
830 782 setting_name = setting.key[len('rhodecode_'):]
831 783 sett = SettingsModel().create_or_update_setting(
832 784 setting_name, form_result[setting.key], setting.type)
833 785 session.add(sett)
834 786
835 787 except Exception:
836 788 log.exception('Exception while updating lab settings')
837 789 h.flash(_('Error occurred during updating labs settings'),
838 790 category='error')
839 791 else:
840 792 Session().commit()
841 793 SettingsModel().invalidate_settings_cache()
842 794 h.flash(_('Updated Labs settings'), category='success')
843 795 return redirect(url('admin_settings_labs'))
844 796
845 797 return htmlfill.render(
846 798 render('admin/settings/settings.mako'),
847 799 defaults=self._form_defaults(),
848 800 encoding='UTF-8',
849 801 force_defaults=False)
850 802
851 803 @HasPermissionAllDecorator('hg.admin')
852 804 def settings_labs(self):
853 805 """GET /admin/settings/labs: All items in the collection"""
854 806 # url('admin_settings_labs')
855 807 if not c.labs_active:
856 808 redirect(url('admin_settings'))
857 809
858 810 c.active = 'labs'
859 811 c.lab_settings = _LAB_SETTINGS
860 812
861 813 return htmlfill.render(
862 814 render('admin/settings/settings.mako'),
863 815 defaults=self._form_defaults(),
864 816 encoding='UTF-8',
865 817 force_defaults=False)
866 818
867 819 def _form_defaults(self):
868 820 defaults = SettingsModel().get_all_settings()
869 821 defaults.update(self._get_hg_ui_settings())
870 822 defaults.update({
871 823 'new_svn_branch': '',
872 824 'new_svn_tag': '',
873 825 })
874 826 return defaults
875 827
876 828
877 829 # :param key: name of the setting including the 'rhodecode_' prefix
878 830 # :param type: the RhodeCodeSetting type to use.
879 831 # :param group: the i18ned group in which we should dispaly this setting
880 832 # :param label: the i18ned label we should display for this setting
881 833 # :param help: the i18ned help we should dispaly for this setting
882 834 LabSetting = collections.namedtuple(
883 835 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
884 836
885 837
886 838 # This list has to be kept in sync with the form
887 839 # rhodecode.model.forms.LabsSettingsForm.
888 840 _LAB_SETTINGS = [
889 841
890 842 ]
@@ -1,62 +1,62 b''
1 1 <div class="panel panel-default">
2 2 <div class="panel-heading">
3 3 <h3 class="panel-title">${_('User Sessions Configuration')}</h3>
4 4 </div>
5 5 <div class="panel-body">
6 6 <%
7 7 elems = [
8 8 (_('Session type'), c.session_model.SESSION_TYPE, ''),
9 9 (_('Session expiration period'), '{} seconds'.format(c.session_conf.get('beaker.session.timeout', 0)), ''),
10 10
11 11 (_('Total sessions'), c.session_count, ''),
12 12 (_('Expired sessions ({} days)').format(c.cleanup_older_days ), c.session_expired_count, ''),
13 13
14 14 ]
15 15 %>
16 16 <dl class="dl-horizontal settings">
17 17 %for dt, dd, tt in elems:
18 18 <dt>${dt}:</dt>
19 19 <dd title="${tt}">${dd}</dd>
20 20 %endfor
21 21 </dl>
22 22 </div>
23 23 </div>
24 24
25 25
26 26 <div class="panel panel-warning">
27 27 <div class="panel-heading">
28 28 <h3 class="panel-title">${_('Cleanup Old Sessions')}</h3>
29 29 </div>
30 30 <div class="panel-body">
31 ${h.secure_form(h.url('admin_settings_sessions_cleanup'), method='post')}
31 ${h.secure_form(h.route_path('admin_settings_sessions_cleanup'), method='post')}
32 32
33 33 <p>
34 34 ${_('Cleanup user sessions that were not active during chosen time frame.')} <br/>
35 35 ${_('After performing this action users whose session will be removed will be required to log in again.')} <br/>
36 36 <strong>${_('Picking `All` will log-out you, and all users in the system.')}</strong>
37 37 </p>
38 38
39 39 <script type="text/javascript">
40 40 $(document).ready(function() {
41 41 $('#expire_days').select2({
42 42 containerCssClass: 'drop-menu',
43 43 dropdownCssClass: 'drop-menu-dropdown',
44 44 dropdownAutoWidth: true,
45 45 minimumResultsForSearch: -1
46 46 });
47 47 });
48 48 </script>
49 49 <select id="expire_days" name="expire_days">
50 50 % for n in [60, 90, 30, 7, 0]:
51 51 <option value="${n}">${'{} days'.format(n) if n != 0 else 'All'}</option>
52 52 % endfor
53 53 </select>
54 54 <button class="btn btn-small" type="submit"
55 55 onclick="return confirm('${_('Confirm to cleanup user sessions')}');">
56 56 ${_('Cleanup sessions')}
57 57 </button>
58 58 ${h.end_form()}
59 59 </div>
60 60 </div>
61 61
62 62
General Comments 0
You need to be logged in to leave comments. Login now