##// END OF EJS Templates
repositories: enabled support for maintenance commands....
marcink -
r1555:9d1f0e57 default
parent child Browse files
Show More
@@ -0,0 +1,33 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21
22 def includeme(config):
23
24 config.add_route(
25 name='repo_maintenance',
26 pattern='/{repo_name:.*?[^/]}/maintenance', repo_route=True)
27
28 config.add_route(
29 name='repo_maintenance_execute',
30 pattern='/{repo_name:.*?[^/]}/maintenance/execute', repo_route=True)
31
32 # Scan module for configuration decorators.
33 config.scan()
@@ -0,0 +1,19 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,70 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.view import view_config
24
25 from rhodecode.apps._base import RepoAppView
26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator,
27 NotAnonymous)
28 from rhodecode.lib import repo_maintenance
29
30 log = logging.getLogger(__name__)
31
32
33 class RepoMaintenanceView(RepoAppView):
34 def load_default_context(self):
35 c = self._get_local_tmpl_context()
36
37 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
38 c.repo_info = self.db_repo
39
40 self._register_global_c(c)
41 return c
42
43 @LoginRequired()
44 @NotAnonymous()
45 @HasRepoPermissionAnyDecorator('repository.admin')
46 @view_config(
47 route_name='repo_maintenance', request_method='GET',
48 renderer='rhodecode:templates/admin/repos/repo_edit.mako')
49 def repo_maintenance(self):
50 c = self.load_default_context()
51 c.active = 'maintenance'
52 maintenance = repo_maintenance.RepoMaintenance()
53 c.executable_tasks = maintenance.get_tasks_for_repo(self.db_repo)
54 return self._get_template_context(c)
55
56 @LoginRequired()
57 @NotAnonymous()
58 @HasRepoPermissionAnyDecorator('repository.admin')
59 @view_config(
60 route_name='repo_maintenance_execute', request_method='GET',
61 renderer='json', xhr=True)
62 def repo_maintenance_execute(self):
63 c = self.load_default_context()
64 c.active = 'maintenance'
65 _ = self.request.translate
66
67 maintenance = repo_maintenance.RepoMaintenance()
68 executed_types = maintenance.execute(self.db_repo)
69
70 return executed_types
@@ -0,0 +1,109 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import logging
21
22 log = logging.getLogger(__name__)
23
24
25 class MaintenanceTask(object):
26 human_name = 'undefined'
27
28 def __init__(self, db_repo):
29 self.db_repo = db_repo
30
31 def run(self):
32 """Execute task and return task human value"""
33 raise NotImplementedError()
34
35
36 class GitGC(MaintenanceTask):
37 human_name = 'GIT Garbage collect'
38
39 def _count_objects(self, repo):
40 stdout, stderr = repo.run_git_command(
41 ['count-objects', '-v'], fail_on_stderr=False)
42
43 errors = ' '
44 objects = ' '.join(stdout.splitlines())
45
46 if stderr:
47 errors = '\nSTD ERR:' + '\n'.join(stderr.splitlines())
48 return objects + errors
49
50 def run(self):
51 output = []
52 instance = self.db_repo.scm_instance()
53
54 objects = self._count_objects(instance)
55 output.append(objects)
56 log.debug('GIT objects:%s', objects)
57
58 stdout, stderr = instance.run_git_command(
59 ['gc', '--aggressive'], fail_on_stderr=False)
60
61 out = 'executed git gc --aggressive'
62 if stderr:
63 out = ''.join(stderr.splitlines())
64
65 elif stdout:
66 out = ''.join(stdout.splitlines())
67
68 output.append(out)
69
70 objects = self._count_objects(instance)
71 log.debug('GIT objects:%s', objects)
72 output.append(objects)
73
74 return '\n'.join(output)
75
76
77 class HGVerify(MaintenanceTask):
78 human_name = 'HG Verify repo'
79
80 def run(self):
81 instance = self.db_repo.scm_instance()
82 res = instance.verify()
83 return res
84
85
86 class RepoMaintenance(object):
87 """
88 Performs maintenance of repository based on it's type
89 """
90 tasks = {
91 'hg': [HGVerify],
92 'git': [GitGC],
93 'svn': [],
94 }
95
96 def get_tasks_for_repo(self, db_repo):
97 """
98 fetches human names of tasks pending for execution for given type of repo
99 """
100 tasks = []
101 for task in self.tasks[db_repo.repo_type]:
102 tasks.append(task.human_name)
103 return tasks
104
105 def execute(self, db_repo):
106 executed_tasks = []
107 for task in self.tasks[db_repo.repo_type]:
108 executed_tasks.append(task(db_repo).run())
109 return executed_tasks
@@ -0,0 +1,58 b''
1 <div class="panel panel-default">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Maintenance')}</h3>
4 </div>
5 <div class="panel-body">
6
7 <p>
8 % if c.executable_tasks:
9 ${_('Perform maintenance tasks for this repo, following tasks will be performed')}:
10 <ol>
11 % for task in c.executable_tasks:
12 <li>${task}</li>
13 % endfor
14 </ol>
15 % else:
16 ${_('No maintenance tasks for this repo available')}
17 % endif
18 </p>
19
20 <div id="results" style="display:none; padding: 10px 0px;"></div>
21
22 % if c.executable_tasks:
23 <div class="form">
24 <div class="fields">
25 <button class="btn btn-small btn-primary" onclick="executeTask();return false">
26 ${_('Run Maintenance')}
27 </button>
28 </div>
29 </div>
30 % endif
31
32 </div>
33 </div>
34
35
36 <script>
37
38 executeTask = function() {
39 var btn = $(this);
40 $('#results').show();
41 $('#results').html('<h4>${_('Performing Maintenance')}...</h4>');
42
43 btn.attr('disabled', 'disabled');
44 btn.addClass('disabled');
45
46 var url = "${h.route_path('repo_maintenance_execute', repo_name=c.repo_info.repo_name)}";
47 var success = function (data) {
48 var displayHtml = $('<pre></pre>');
49
50 $(displayHtml).append(data);
51 $('#results').html(displayHtml);
52 btn.removeAttr('disabled');
53 btn.removeClass('disabled');
54 };
55 ajaxGET(url, success, null);
56
57 }
58 </script>
@@ -1,506 +1,507 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 Pylons middleware initialization
23 23 """
24 24 import logging
25 25 from collections import OrderedDict
26 26
27 27 from paste.registry import RegistryManager
28 28 from paste.gzipper import make_gzip_middleware
29 29 from pylons.wsgiapp import PylonsApp
30 30 from pyramid.authorization import ACLAuthorizationPolicy
31 31 from pyramid.config import Configurator
32 32 from pyramid.settings import asbool, aslist
33 33 from pyramid.wsgi import wsgiapp
34 34 from pyramid.httpexceptions import (
35 35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 36 from pyramid.events import ApplicationCreated
37 37 from pyramid.renderers import render_to_response
38 38 from routes.middleware import RoutesMiddleware
39 39 import routes.util
40 40
41 41 import rhodecode
42 42 from rhodecode.model import meta
43 43 from rhodecode.config import patches
44 44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 45 from rhodecode.config.environment import (
46 46 load_environment, load_pyramid_environment)
47 47 from rhodecode.lib.middleware import csrf
48 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 49 from rhodecode.lib.middleware.error_handling import (
50 50 PylonsErrorHandlingMiddleware)
51 51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 55 from rhodecode.subscribers import (
56 56 scan_repositories_if_enabled, write_metadata_if_needed,
57 57 write_js_routes_if_enabled)
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 # this is used to avoid avoid the route lookup overhead in routesmiddleware
64 64 # for certain routes which won't go to pylons to - eg. static files, debugger
65 65 # it is only needed for the pylons migration and can be removed once complete
66 66 class SkippableRoutesMiddleware(RoutesMiddleware):
67 67 """ Routes middleware that allows you to skip prefixes """
68 68
69 69 def __init__(self, *args, **kw):
70 70 self.skip_prefixes = kw.pop('skip_prefixes', [])
71 71 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
72 72
73 73 def __call__(self, environ, start_response):
74 74 for prefix in self.skip_prefixes:
75 75 if environ['PATH_INFO'].startswith(prefix):
76 76 # added to avoid the case when a missing /_static route falls
77 77 # through to pylons and causes an exception as pylons is
78 78 # expecting wsgiorg.routingargs to be set in the environ
79 79 # by RoutesMiddleware.
80 80 if 'wsgiorg.routing_args' not in environ:
81 81 environ['wsgiorg.routing_args'] = (None, {})
82 82 return self.app(environ, start_response)
83 83
84 84 return super(SkippableRoutesMiddleware, self).__call__(
85 85 environ, start_response)
86 86
87 87
88 88 def make_app(global_conf, static_files=True, **app_conf):
89 89 """Create a Pylons WSGI application and return it
90 90
91 91 ``global_conf``
92 92 The inherited configuration for this application. Normally from
93 93 the [DEFAULT] section of the Paste ini file.
94 94
95 95 ``app_conf``
96 96 The application's local configuration. Normally specified in
97 97 the [app:<name>] section of the Paste ini file (where <name>
98 98 defaults to main).
99 99
100 100 """
101 101 # Apply compatibility patches
102 102 patches.kombu_1_5_1_python_2_7_11()
103 103 patches.inspect_getargspec()
104 104
105 105 # Configure the Pylons environment
106 106 config = load_environment(global_conf, app_conf)
107 107
108 108 # The Pylons WSGI app
109 109 app = PylonsApp(config=config)
110 110 if rhodecode.is_test:
111 111 app = csrf.CSRFDetector(app)
112 112
113 113 expected_origin = config.get('expected_origin')
114 114 if expected_origin:
115 115 # The API can be accessed from other Origins.
116 116 app = csrf.OriginChecker(app, expected_origin,
117 117 skip_urls=[routes.util.url_for('api')])
118 118
119 119 # Establish the Registry for this application
120 120 app = RegistryManager(app)
121 121
122 122 app.config = config
123 123
124 124 return app
125 125
126 126
127 127 def make_pyramid_app(global_config, **settings):
128 128 """
129 129 Constructs the WSGI application based on Pyramid and wraps the Pylons based
130 130 application.
131 131
132 132 Specials:
133 133
134 134 * We migrate from Pylons to Pyramid. While doing this, we keep both
135 135 frameworks functional. This involves moving some WSGI middlewares around
136 136 and providing access to some data internals, so that the old code is
137 137 still functional.
138 138
139 139 * The application can also be integrated like a plugin via the call to
140 140 `includeme`. This is accompanied with the other utility functions which
141 141 are called. Changing this should be done with great care to not break
142 142 cases when these fragments are assembled from another place.
143 143
144 144 """
145 145 # The edition string should be available in pylons too, so we add it here
146 146 # before copying the settings.
147 147 settings.setdefault('rhodecode.edition', 'Community Edition')
148 148
149 149 # As long as our Pylons application does expect "unprepared" settings, make
150 150 # sure that we keep an unmodified copy. This avoids unintentional change of
151 151 # behavior in the old application.
152 152 settings_pylons = settings.copy()
153 153
154 154 sanitize_settings_and_apply_defaults(settings)
155 155 config = Configurator(settings=settings)
156 156 add_pylons_compat_data(config.registry, global_config, settings_pylons)
157 157
158 158 load_pyramid_environment(global_config, settings)
159 159
160 160 includeme_first(config)
161 161 includeme(config)
162 162 pyramid_app = config.make_wsgi_app()
163 163 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
164 164 pyramid_app.config = config
165 165
166 166 # creating the app uses a connection - return it after we are done
167 167 meta.Session.remove()
168 168
169 169 return pyramid_app
170 170
171 171
172 172 def make_not_found_view(config):
173 173 """
174 174 This creates the view which should be registered as not-found-view to
175 175 pyramid. Basically it contains of the old pylons app, converted to a view.
176 176 Additionally it is wrapped by some other middlewares.
177 177 """
178 178 settings = config.registry.settings
179 179 vcs_server_enabled = settings['vcs.server.enable']
180 180
181 181 # Make pylons app from unprepared settings.
182 182 pylons_app = make_app(
183 183 config.registry._pylons_compat_global_config,
184 184 **config.registry._pylons_compat_settings)
185 185 config.registry._pylons_compat_config = pylons_app.config
186 186
187 187 # Appenlight monitoring.
188 188 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
189 189 pylons_app, settings)
190 190
191 191 # The pylons app is executed inside of the pyramid 404 exception handler.
192 192 # Exceptions which are raised inside of it are not handled by pyramid
193 193 # again. Therefore we add a middleware that invokes the error handler in
194 194 # case of an exception or error response. This way we return proper error
195 195 # HTML pages in case of an error.
196 196 reraise = (settings.get('debugtoolbar.enabled', False) or
197 197 rhodecode.disable_error_handler)
198 198 pylons_app = PylonsErrorHandlingMiddleware(
199 199 pylons_app, error_handler, reraise)
200 200
201 201 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
202 202 # view to handle the request. Therefore it is wrapped around the pylons
203 203 # app. It has to be outside of the error handling otherwise error responses
204 204 # from the vcsserver are converted to HTML error pages. This confuses the
205 205 # command line tools and the user won't get a meaningful error message.
206 206 if vcs_server_enabled:
207 207 pylons_app = VCSMiddleware(
208 208 pylons_app, settings, appenlight_client, registry=config.registry)
209 209
210 210 # Convert WSGI app to pyramid view and return it.
211 211 return wsgiapp(pylons_app)
212 212
213 213
214 214 def add_pylons_compat_data(registry, global_config, settings):
215 215 """
216 216 Attach data to the registry to support the Pylons integration.
217 217 """
218 218 registry._pylons_compat_global_config = global_config
219 219 registry._pylons_compat_settings = settings
220 220
221 221
222 222 def error_handler(exception, request):
223 223 import rhodecode
224 224 from rhodecode.lib.utils2 import AttributeDict
225 225
226 226 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
227 227
228 228 base_response = HTTPInternalServerError()
229 229 # prefer original exception for the response since it may have headers set
230 230 if isinstance(exception, HTTPException):
231 231 base_response = exception
232 232
233 233 def is_http_error(response):
234 234 # error which should have traceback
235 235 return response.status_code > 499
236 236
237 237 if is_http_error(base_response):
238 238 log.exception(
239 239 'error occurred handling this request for path: %s', request.path)
240 240
241 241 c = AttributeDict()
242 242 c.error_message = base_response.status
243 243 c.error_explanation = base_response.explanation or str(base_response)
244 244 c.visual = AttributeDict()
245 245
246 246 c.visual.rhodecode_support_url = (
247 247 request.registry.settings.get('rhodecode_support_url') or
248 248 request.route_url('rhodecode_support')
249 249 )
250 250 c.redirect_time = 0
251 251 c.rhodecode_name = rhodecode_title
252 252 if not c.rhodecode_name:
253 253 c.rhodecode_name = 'Rhodecode'
254 254
255 255 c.causes = []
256 256 if hasattr(base_response, 'causes'):
257 257 c.causes = base_response.causes
258 258
259 259 response = render_to_response(
260 260 '/errors/error_document.mako', {'c': c}, request=request,
261 261 response=base_response)
262 262
263 263 return response
264 264
265 265
266 266 def includeme(config):
267 267 settings = config.registry.settings
268 268
269 269 # plugin information
270 270 config.registry.rhodecode_plugins = OrderedDict()
271 271
272 272 config.add_directive(
273 273 'register_rhodecode_plugin', register_rhodecode_plugin)
274 274
275 275 if asbool(settings.get('appenlight', 'false')):
276 276 config.include('appenlight_client.ext.pyramid_tween')
277 277
278 278 # Includes which are required. The application would fail without them.
279 279 config.include('pyramid_mako')
280 280 config.include('pyramid_beaker')
281 281
282 282 config.include('rhodecode.authentication')
283 283 config.include('rhodecode.integrations')
284 284
285 285 # apps
286 286 config.include('rhodecode.apps._base')
287 287
288 288 config.include('rhodecode.apps.admin')
289 289 config.include('rhodecode.apps.channelstream')
290 290 config.include('rhodecode.apps.login')
291 config.include('rhodecode.apps.repository')
291 292 config.include('rhodecode.apps.user_profile')
292 293 config.include('rhodecode.apps.my_account')
293 294 config.include('rhodecode.apps.svn_support')
294 295
295 296 config.include('rhodecode.tweens')
296 297 config.include('rhodecode.api')
297 298
298 299 config.add_route(
299 300 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
300 301
301 302 config.add_translation_dirs('rhodecode:i18n/')
302 303 settings['default_locale_name'] = settings.get('lang', 'en')
303 304
304 305 # Add subscribers.
305 306 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
306 307 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
307 308 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
308 309
309 310 # Set the authorization policy.
310 311 authz_policy = ACLAuthorizationPolicy()
311 312 config.set_authorization_policy(authz_policy)
312 313
313 314 # Set the default renderer for HTML templates to mako.
314 315 config.add_mako_renderer('.html')
315 316
316 317 # include RhodeCode plugins
317 318 includes = aslist(settings.get('rhodecode.includes', []))
318 319 for inc in includes:
319 320 config.include(inc)
320 321
321 322 # This is the glue which allows us to migrate in chunks. By registering the
322 323 # pylons based application as the "Not Found" view in Pyramid, we will
323 324 # fallback to the old application each time the new one does not yet know
324 325 # how to handle a request.
325 326 config.add_notfound_view(make_not_found_view(config))
326 327
327 328 if not settings.get('debugtoolbar.enabled', False):
328 329 # if no toolbar, then any exception gets caught and rendered
329 330 config.add_view(error_handler, context=Exception)
330 331
331 332 config.add_view(error_handler, context=HTTPError)
332 333
333 334
334 335 def includeme_first(config):
335 336 # redirect automatic browser favicon.ico requests to correct place
336 337 def favicon_redirect(context, request):
337 338 return HTTPFound(
338 339 request.static_path('rhodecode:public/images/favicon.ico'))
339 340
340 341 config.add_view(favicon_redirect, route_name='favicon')
341 342 config.add_route('favicon', '/favicon.ico')
342 343
343 344 def robots_redirect(context, request):
344 345 return HTTPFound(
345 346 request.static_path('rhodecode:public/robots.txt'))
346 347
347 348 config.add_view(robots_redirect, route_name='robots')
348 349 config.add_route('robots', '/robots.txt')
349 350
350 351 config.add_static_view(
351 352 '_static/deform', 'deform:static')
352 353 config.add_static_view(
353 354 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
354 355
355 356
356 357 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
357 358 """
358 359 Apply outer WSGI middlewares around the application.
359 360
360 361 Part of this has been moved up from the Pylons layer, so that the
361 362 data is also available if old Pylons code is hit through an already ported
362 363 view.
363 364 """
364 365 settings = config.registry.settings
365 366
366 367 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
367 368 pyramid_app = HttpsFixup(pyramid_app, settings)
368 369
369 370 # Add RoutesMiddleware to support the pylons compatibility tween during
370 371 # migration to pyramid.
371 372 pyramid_app = SkippableRoutesMiddleware(
372 373 pyramid_app, config.registry._pylons_compat_config['routes.map'],
373 374 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
374 375
375 376 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
376 377
377 378 if settings['gzip_responses']:
378 379 pyramid_app = make_gzip_middleware(
379 380 pyramid_app, settings, compress_level=1)
380 381
381 382 # this should be the outer most middleware in the wsgi stack since
382 383 # middleware like Routes make database calls
383 384 def pyramid_app_with_cleanup(environ, start_response):
384 385 try:
385 386 return pyramid_app(environ, start_response)
386 387 finally:
387 388 # Dispose current database session and rollback uncommitted
388 389 # transactions.
389 390 meta.Session.remove()
390 391
391 392 # In a single threaded mode server, on non sqlite db we should have
392 393 # '0 Current Checked out connections' at the end of a request,
393 394 # if not, then something, somewhere is leaving a connection open
394 395 pool = meta.Base.metadata.bind.engine.pool
395 396 log.debug('sa pool status: %s', pool.status())
396 397
397 398
398 399 return pyramid_app_with_cleanup
399 400
400 401
401 402 def sanitize_settings_and_apply_defaults(settings):
402 403 """
403 404 Applies settings defaults and does all type conversion.
404 405
405 406 We would move all settings parsing and preparation into this place, so that
406 407 we have only one place left which deals with this part. The remaining parts
407 408 of the application would start to rely fully on well prepared settings.
408 409
409 410 This piece would later be split up per topic to avoid a big fat monster
410 411 function.
411 412 """
412 413
413 414 # Pyramid's mako renderer has to search in the templates folder so that the
414 415 # old templates still work. Ported and new templates are expected to use
415 416 # real asset specifications for the includes.
416 417 mako_directories = settings.setdefault('mako.directories', [
417 418 # Base templates of the original Pylons application
418 419 'rhodecode:templates',
419 420 ])
420 421 log.debug(
421 422 "Using the following Mako template directories: %s",
422 423 mako_directories)
423 424
424 425 # Default includes, possible to change as a user
425 426 pyramid_includes = settings.setdefault('pyramid.includes', [
426 427 'rhodecode.lib.middleware.request_wrapper',
427 428 ])
428 429 log.debug(
429 430 "Using the following pyramid.includes: %s",
430 431 pyramid_includes)
431 432
432 433 # TODO: johbo: Re-think this, usually the call to config.include
433 434 # should allow to pass in a prefix.
434 435 settings.setdefault('rhodecode.api.url', '/_admin/api')
435 436
436 437 # Sanitize generic settings.
437 438 _list_setting(settings, 'default_encoding', 'UTF-8')
438 439 _bool_setting(settings, 'is_test', 'false')
439 440 _bool_setting(settings, 'gzip_responses', 'false')
440 441
441 442 # Call split out functions that sanitize settings for each topic.
442 443 _sanitize_appenlight_settings(settings)
443 444 _sanitize_vcs_settings(settings)
444 445
445 446 return settings
446 447
447 448
448 449 def _sanitize_appenlight_settings(settings):
449 450 _bool_setting(settings, 'appenlight', 'false')
450 451
451 452
452 453 def _sanitize_vcs_settings(settings):
453 454 """
454 455 Applies settings defaults and does type conversion for all VCS related
455 456 settings.
456 457 """
457 458 _string_setting(settings, 'vcs.svn.compatible_version', '')
458 459 _string_setting(settings, 'git_rev_filter', '--all')
459 460 _string_setting(settings, 'vcs.hooks.protocol', 'http')
460 461 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
461 462 _string_setting(settings, 'vcs.server', '')
462 463 _string_setting(settings, 'vcs.server.log_level', 'debug')
463 464 _string_setting(settings, 'vcs.server.protocol', 'http')
464 465 _bool_setting(settings, 'startup.import_repos', 'false')
465 466 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
466 467 _bool_setting(settings, 'vcs.server.enable', 'true')
467 468 _bool_setting(settings, 'vcs.start_server', 'false')
468 469 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
469 470 _int_setting(settings, 'vcs.connection_timeout', 3600)
470 471
471 472 # Support legacy values of vcs.scm_app_implementation. Legacy
472 473 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
473 474 # which is now mapped to 'http'.
474 475 scm_app_impl = settings['vcs.scm_app_implementation']
475 476 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
476 477 settings['vcs.scm_app_implementation'] = 'http'
477 478
478 479
479 480 def _int_setting(settings, name, default):
480 481 settings[name] = int(settings.get(name, default))
481 482
482 483
483 484 def _bool_setting(settings, name, default):
484 485 input = settings.get(name, default)
485 486 if isinstance(input, unicode):
486 487 input = input.encode('utf8')
487 488 settings[name] = asbool(input)
488 489
489 490
490 491 def _list_setting(settings, name, default):
491 492 raw_value = settings.get(name, default)
492 493
493 494 old_separator = ','
494 495 if old_separator in raw_value:
495 496 # If we get a comma separated list, pass it to our own function.
496 497 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
497 498 else:
498 499 # Otherwise we assume it uses pyramids space/newline separation.
499 500 settings[name] = aslist(raw_value)
500 501
501 502
502 503 def _string_setting(settings, name, default, lower=True):
503 504 value = settings.get(name, default)
504 505 if lower:
505 506 value = value.lower()
506 507 settings[name] = value
@@ -1,100 +1,103 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##
3 3 ## See also repo_settings.html
4 4 ##
5 5 <%inherit file="/base/base.mako"/>
6 6
7 7 <%def name="title()">
8 8 ${_('%s repository settings') % c.repo_info.repo_name}
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Settings')}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_nav()">
19 19 ${self.menu_items(active='repositories')}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_subnav()">
23 23 ${self.repo_menu(active='options')}
24 24 </%def>
25 25
26 26 <%def name="main_content()">
27 27 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
28 28 </%def>
29 29
30 30
31 31 <%def name="main()">
32 32 <div class="box">
33 33 <div class="title">
34 34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37
38 38 <div class="sidebar-col-wrapper scw-small">
39 39 ##main
40 40 <div class="sidebar">
41 41 <ul class="nav nav-pills nav-stacked">
42 42 <li class="${'active' if c.active=='settings' else ''}">
43 43 <a href="${h.url('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
44 44 </li>
45 45 <li class="${'active' if c.active=='permissions' else ''}">
46 46 <a href="${h.url('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
47 47 </li>
48 48 <li class="${'active' if c.active=='advanced' else ''}">
49 49 <a href="${h.url('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
50 50 </li>
51 51 <li class="${'active' if c.active=='vcs' else ''}">
52 52 <a href="${h.url('repo_vcs_settings', repo_name=c.repo_name)}">${_('VCS')}</a>
53 53 </li>
54 54 <li class="${'active' if c.active=='fields' else ''}">
55 55 <a href="${h.url('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
56 56 </li>
57 57 <li class="${'active' if c.active=='issuetracker' else ''}">
58 58 <a href="${h.url('repo_settings_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
59 59 </li>
60 60 <li class="${'active' if c.active=='caches' else ''}">
61 61 <a href="${h.url('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
62 62 </li>
63 63 %if c.repo_info.repo_type != 'svn':
64 64 <li class="${'active' if c.active=='remote' else ''}">
65 65 <a href="${h.url('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote')}</a>
66 66 </li>
67 67 %endif
68 68 <li class="${'active' if c.active=='statistics' else ''}">
69 69 <a href="${h.url('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
70 70 </li>
71 71 <li class="${'active' if c.active=='integrations' else ''}">
72 72 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
73 73 </li>
74 <li class="${'active' if c.active=='maintenance' else ''}">
75 <a href="${h.route_path('repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
76 </li>
74 77 ## TODO: dan: replace repo navigation with navlist registry like with
75 78 ## admin menu. First must find way to allow runtime configuration
76 79 ## it to account for the c.repo_info.repo_type != 'svn' call above
77 80 <%
78 81 reviewer_settings = False
79 82 try:
80 83 import rc_reviewers
81 84 reviewer_settings = True
82 85 except ImportError:
83 86 pass
84 87 %>
85 88 %if reviewer_settings:
86 89 <li class="${'active' if c.active=='reviewers' else ''}">
87 90 <a href="${h.route_path('repo_reviewers_home', repo_name=c.repo_name)}">${_('Reviewers')}</a>
88 91 </li>
89 92 %endif
90 93 </ul>
91 94 </div>
92 95
93 96 <div class="main-content-full-width">
94 97 ${self.main_content()}
95 98 </div>
96 99
97 100 </div>
98 101 </div>
99 102
100 103 </%def> No newline at end of file
@@ -1,295 +1,295 b''
1 1 ${h.secure_form(url('admin_settings_global'), method='post')}
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading" id="branding-options">
5 5 <h3 class="panel-title">${_('Branding')} <a class="permalink" href="#branding-options"> ΒΆ</a></h3>
6 6 </div>
7 7 <div class="panel-body">
8 8 <div class="label">
9 9 <label for="rhodecode_title">${_('Title')}</label>
10 10 </div>
11 11 <div class="field input">
12 12 ${h.text('rhodecode_title',size=60)}
13 13 </div>
14 14 <div class="field">
15 15 <span class="help-block">
16 16 ${_('Set a custom title for your RhodeCode instance (limited to 40 characters).')}
17 17 </span>
18 18 </div>
19 19 <div class="label">
20 20 <label for="rhodecode_realm">${_('HTTP[S] authentication realm')}</label>
21 21 </div>
22 22 <div class="field input">
23 23 ${h.text('rhodecode_realm',size=60)}
24 24 </div>
25 25 <div class="field">
26 26 <span class="help-block">
27 27 ${_('Set a custom text that is shown as authentication message to clients trying to connect.')}
28 28 </span>
29 29 </div>
30 30 </div>
31 31 </div>
32 32
33 33
34 34 <div class="panel panel-default">
35 35 <div class="panel-heading" id="personal-group-options">
36 36 <h3 class="panel-title">${_('Personal Repository Group')} <a class="permalink" href="#personal-group-options"> ΒΆ</a></h3>
37 37 </div>
38 38 <div class="panel-body">
39 39 <div class="checkbox">
40 40 ${h.checkbox('rhodecode_create_personal_repo_group','True')}
41 41 <label for="rhodecode_create_personal_repo_group">${_('Create Personal Repository Group')}</label>
42 42 </div>
43 43 <span class="help-block">
44 44 ${_('Always create Personal Repository Groups for new users.')} <br/>
45 45 ${_('When creating new users from add user form or API you can still turn this off via a checkbox or flag')}
46 46 </span>
47 47
48 48 <div class="label">
49 49 <label for="rhodecode_personal_repo_group_pattern">${_('Personal Repo Group Pattern')}</label>
50 50 </div>
51 51 <div class="field input">
52 52 ${h.text('rhodecode_personal_repo_group_pattern',size=60, placeholder=c.personal_repo_group_default_pattern)}
53 53 </div>
54 54 <span class="help-block">
55 55 ${_('Pattern used to create Personal Repository Groups. Prefix can be other existing repository group path[s], eg. /u/${username}')} <br/>
56 56 ${_('Available variables are currently ${username} and ${user_id}')}
57 57 </span>
58 58 </div>
59 59 </div>
60 60
61 61
62 62 <div class="panel panel-default">
63 63 <div class="panel-heading" id="captcha-options">
64 64 <h3 class="panel-title">${_('Registration Captcha')} <a class="permalink" href="#captcha-options"> ΒΆ</a></h3>
65 65 </div>
66 66 <div class="panel-body">
67 67 <div class="label">
68 68 <label for="rhodecode_captcha_public_key">${_('Google ReCaptcha public key')}</label>
69 69 </div>
70 70 <div class="field input">
71 71 ${h.text('rhodecode_captcha_public_key',size=60)}
72 72 </div>
73 73 <div class="field">
74 74 <span class="help-block">
75 75 ${_('Public key for reCaptcha system.')}
76 76 </span>
77 77 </div>
78 78
79 79 <div class="label">
80 80 <label for="rhodecode_captcha_private_key">${_('Google ReCaptcha private key')}</label>
81 81 </div>
82 82 <div class="field input">
83 83 ${h.text('rhodecode_captcha_private_key',size=60)}
84 84 </div>
85 85 <div class="field">
86 86 <span class="help-block">
87 87 ${_('Private key for reCaptcha system. Setting this value will enable captcha on registration')}
88 88 </span>
89 89 </div>
90 90 </div>
91 91 </div>
92 92
93 93 <div class="panel panel-default">
94 94 <div class="panel-heading" id="header-code-options">
95 95 <h3 class="panel-title">${_('Custom Header Code')} <a class="permalink" href="#header-code-options"> ΒΆ</a></h3>
96 96 </div>
97 97 <div class="panel-body">
98 98 <div class="select">
99 99 <select id="pre_template" >
100 100 <option value="#">${_('Templates...')}</option>
101 101 <option value="ga">Google Analytics</option>
102 102 <option value="clicky">Clicky</option>
103 103 <option value="server_announce">${_('Server Announcement')}</option>
104 104 <option value="flash_filtering">${_('Flash message filtering')}</option>
105 105 </select>
106 106 </div>
107 107 <div style="padding: 10px 0px"></div>
108 108 <div class="textarea text-area">
109 109 ${h.textarea('rhodecode_pre_code',cols=23,rows=5,class_="medium")}
110 110 <span class="help-block">${_('Custom js/css code added at the end of the <header/> tag.')}
111 111 ${_('Use <script/> or <css/> tags to define custom styling or scripting')}</span>
112 112 </div>
113 113 </div>
114 114 </div>
115 115
116 116 <div class="panel panel-default">
117 117 <div class="panel-heading" id="footer-code-options">
118 118 <h3 class="panel-title">${_('Custom Footer Code')} <a class="permalink" href="#footer-code-options"> ΒΆ</a></h3>
119 119 </div>
120 120 <div class="panel-body">
121 121 <div class="select">
122 122 <select id="post_template" >
123 123 <option value="#">${_('Templates...')}</option>
124 124 <option value="ga">Google Analytics</option>
125 125 <option value="clicky">Clicky</option>
126 126 <option value="server_announce">${_('Server Announcement')}</option>
127 127 </select>
128 128 </div>
129 129 <div style="padding: 10px 0px"></div>
130 130 <div class="textarea text-area">
131 131 ${h.textarea('rhodecode_post_code',cols=23,rows=5, class_="medium")}
132 132 <span class="help-block">${_('Custom js/css code added at the end of the <body> tag.')}
133 133 ${_('Use <script> or <css> tags to define custom styling or scripting')}</span>
134 134 </div>
135 135 </div>
136 136 </div>
137 137
138 138 <div class="buttons">
139 139 ${h.submit('save',_('Save settings'),class_="btn")}
140 140 ${h.reset('reset',_('Reset'),class_="btn")}
141 141 </div>
142 142 ${h.end_form()}
143 143
144 144
145 145
146 146 ## TEMPLATES ##
147 147 ###############
148 148
149 149 <script id="ga_tmpl" type="text/x-template">
150 150 <%text filter="h">
151 151 <script>
152 152 // Google Analytics
153 153 // Put your Google Analytics code instead of _GACODE_
154 154 var _gaq_code = '_GACODE_';
155 155 var _gaq = _gaq || [];
156 156 _gaq.push(['_setAccount', _gaq_code]);
157 157 _gaq.push(['_trackPageview']);
158 158
159 159 (function() {
160 160 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
161 161 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
162 162 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
163 163 })();
164 164
165 165 rhodecode_statechange_callback = function(url, data){
166 166 // ANALYTICS callback on html5 history state changed
167 167 // triggered by file browser, url is the new url,
168 168 // data is extra info passed from the State object
169 169 if (typeof window._gaq !== 'undefined') {
170 170 _gaq.push(['_trackPageview', url]);
171 171 }
172 172 };
173 173 </script>
174 174 </%text>
175 175 </script>
176 176
177 177
178 178
179 179 <script id="clicky_tmpl" type="text/x-template">
180 180 <%text filter="h">
181 181 <script src="//static.getclicky.com/js" type="text/javascript"></script>
182 182 <script type="text/javascript">
183 183 // Clicky Analytics - should be used in the footer code section.
184 184 // Put your Clicky code instead of _CLICKYCODE_ here,
185 185 // and below in the <img> tag.
186 186 var _cl_code = _CLICKYCODE_;
187 187 try{clicky.init(_cl_code);}catch(e){}
188 188
189 189 rhodecode_statechange_callback = function(url, data){
190 190 // ANALYTICS callback on html5 history state changed
191 191 // triggered by file browser, url is the new url,
192 192 // data is extra info passed from the State object
193 193 if (typeof window.clicky !== 'undefined') {
194 194 clicky.log(url);
195 195 }
196 196 }
197 197 </script>
198 198 <noscript>
199 199 // Put your clicky code in the src file.
200 200 <p><img alt="Clicky" width="1" height="1"
201 201 src="//in.getclicky.com/_CLICKYCODE_ns.gif" /></p>
202 202 </noscript>
203 203 </%text>
204 204 </script>
205 205
206 206
207 207
208 208 <script id="server_announce_tmpl" type='text/x-template'>
209 209 <%text filter="h">
210 210 <script>
211 211 // Server announcement displayed on the top of the page.
212 // This can be used to send a global maintainance messages or other
212 // This can be used to send a global maintenance messages or other
213 213 // important messages to all users of the RhodeCode Enterprise system.
214 214
215 215 $(document).ready(function(e){
216 216
217 217 // EDIT - put your message below
218 218 var message = "TYPE YOUR MESSAGE HERE";
219 219
220 220 // EDIT - choose "info"/"warning"/"error"/"success"/"neutral" as appropriate
221 221 var alert_level = "info";
222 222
223 223 $("#body").prepend(
224 224 ("<div id='server-announcement' class='"+alert_level+"'>_MSG_"+"</div>").replace("_MSG_", message)
225 225 )
226 226 })
227 227 </script>
228 228 </%text>
229 229 </script>
230 230
231 231 <script id="flash_filtering_tmpl" type='text/x-template'>
232 232 <%text filter="h">
233 233 <script>
234 234 // This filters out some flash messages before they are presented to user
235 235 // based on their contents. Could be used to filter out warnings/errors
236 236 // of license messages
237 237
238 238 var filteredMessages = [];
239 239 for(var i =0; i< alertMessagePayloads.length; i++){
240 240 if (typeof alertMessagePayloads[i].message.subdata.subtype !== 'undefined' &&
241 241 alertMessagePayloads[i].message.subdata.subtype.indexOf('rc_license') !== -1){
242 242 continue
243 243 }
244 244 filteredMessages.push(alertMessagePayloads[i]);
245 245 }
246 246 alertMessagePayloads = filteredMessages;
247 247 </script>
248 248 </%text>
249 249 </script>
250 250
251 251 <script>
252 252 var pre_cm = initCodeMirror('rhodecode_pre_code', '', false);
253 253 var pre_old = pre_cm.getValue();
254 254
255 255 var post_cm = initCodeMirror('rhodecode_post_code', '', false);
256 256 var post_old = post_cm.getValue();
257 257
258 258 var get_data = function(type, old){
259 259 var get_tmpl = function(tmpl_name){
260 260 // unescape some stuff
261 261 return htmlEnDeCode.htmlDecode($('#'+tmpl_name+'_tmpl').html());
262 262 };
263 263 return {
264 264 '#': old,
265 265 'ga': get_tmpl('ga'),
266 266 'clicky': get_tmpl('clicky'),
267 267 'server_announce': get_tmpl('server_announce'),
268 268 'flash_filtering': get_tmpl('flash_filtering')
269 269 }[type]
270 270 };
271 271
272 272 $('#pre_template').select2({
273 273 containerCssClass: 'drop-menu',
274 274 dropdownCssClass: 'drop-menu-dropdown',
275 275 dropdownAutoWidth: true,
276 276 minimumResultsForSearch: -1
277 277 });
278 278
279 279 $('#post_template').select2({
280 280 containerCssClass: 'drop-menu',
281 281 dropdownCssClass: 'drop-menu-dropdown',
282 282 dropdownAutoWidth: true,
283 283 minimumResultsForSearch: -1
284 284 });
285 285
286 286 $('#post_template').on('change', function(e){
287 287 var sel = this.value;
288 288 post_cm.setValue(get_data(sel, post_old))
289 289 });
290 290
291 291 $('#pre_template').on('change', function(e){
292 292 var sel = this.value;
293 293 pre_cm.setValue(get_data(sel, pre_old))
294 294 })
295 295 </script>
General Comments 0
You need to be logged in to leave comments. Login now