Show More
@@ -0,0 +1,153 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2018-2018 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 os | |
|
21 | import logging | |
|
22 | ||
|
23 | from pyramid.httpexceptions import HTTPFound | |
|
24 | from pyramid.view import view_config | |
|
25 | ||
|
26 | from rhodecode.apps._base import BaseAppView | |
|
27 | from rhodecode.apps.admin.navigation import navigation_list | |
|
28 | from rhodecode.lib import helpers as h | |
|
29 | from rhodecode.lib.auth import ( | |
|
30 | LoginRequired, HasPermissionAllDecorator, CSRFRequired) | |
|
31 | from rhodecode.lib.utils2 import time_to_utcdatetime | |
|
32 | from rhodecode.lib import exc_tracking | |
|
33 | ||
|
34 | log = logging.getLogger(__name__) | |
|
35 | ||
|
36 | ||
|
37 | class ExceptionsTrackerView(BaseAppView): | |
|
38 | def load_default_context(self): | |
|
39 | c = self._get_local_tmpl_context() | |
|
40 | c.navlist = navigation_list(self.request) | |
|
41 | return c | |
|
42 | ||
|
43 | def count_all_exceptions(self): | |
|
44 | exc_store_path = exc_tracking.get_exc_store() | |
|
45 | count = 0 | |
|
46 | for fname in os.listdir(exc_store_path): | |
|
47 | parts = fname.split('_', 2) | |
|
48 | if not len(parts) == 3: | |
|
49 | continue | |
|
50 | count +=1 | |
|
51 | return count | |
|
52 | ||
|
53 | def get_all_exceptions(self, read_metadata=False, limit=None): | |
|
54 | exc_store_path = exc_tracking.get_exc_store() | |
|
55 | exception_list = [] | |
|
56 | ||
|
57 | def key_sorter(val): | |
|
58 | try: | |
|
59 | return val.split('_')[-1] | |
|
60 | except Exception: | |
|
61 | return 0 | |
|
62 | count = 0 | |
|
63 | for fname in reversed(sorted(os.listdir(exc_store_path), key=key_sorter)): | |
|
64 | ||
|
65 | parts = fname.split('_', 2) | |
|
66 | if not len(parts) == 3: | |
|
67 | continue | |
|
68 | ||
|
69 | exc_id, app_type, exc_timestamp = parts | |
|
70 | ||
|
71 | exc = {'exc_id': exc_id, 'app_type': app_type, 'exc_type': 'unknown', | |
|
72 | 'exc_utc_date': '', 'exc_timestamp': exc_timestamp} | |
|
73 | ||
|
74 | if read_metadata: | |
|
75 | full_path = os.path.join(exc_store_path, fname) | |
|
76 | # we can read our metadata | |
|
77 | with open(full_path, 'rb') as f: | |
|
78 | exc_metadata = exc_tracking.exc_unserialize(f.read()) | |
|
79 | exc.update(exc_metadata) | |
|
80 | ||
|
81 | # convert our timestamp to a date obj, for nicer representation | |
|
82 | exc['exc_utc_date'] = time_to_utcdatetime(exc['exc_timestamp']) | |
|
83 | exception_list.append(exc) | |
|
84 | ||
|
85 | count += 1 | |
|
86 | if limit and count >= limit: | |
|
87 | break | |
|
88 | return exception_list | |
|
89 | ||
|
90 | @LoginRequired() | |
|
91 | @HasPermissionAllDecorator('hg.admin') | |
|
92 | @view_config( | |
|
93 | route_name='admin_settings_exception_tracker', request_method='GET', | |
|
94 | renderer='rhodecode:templates/admin/settings/settings.mako') | |
|
95 | def browse_exceptions(self): | |
|
96 | _ = self.request.translate | |
|
97 | c = self.load_default_context() | |
|
98 | c.active = 'exceptions_browse' | |
|
99 | c.limit = self.request.GET.get('limit', 50) | |
|
100 | c.exception_list = self.get_all_exceptions(read_metadata=True, limit=c.limit) | |
|
101 | c.exception_list_count = self.count_all_exceptions() | |
|
102 | c.exception_store_dir = exc_tracking.get_exc_store() | |
|
103 | return self._get_template_context(c) | |
|
104 | ||
|
105 | @LoginRequired() | |
|
106 | @HasPermissionAllDecorator('hg.admin') | |
|
107 | @view_config( | |
|
108 | route_name='admin_settings_exception_tracker_show', request_method='GET', | |
|
109 | renderer='rhodecode:templates/admin/settings/settings.mako') | |
|
110 | def exception_show(self): | |
|
111 | _ = self.request.translate | |
|
112 | c = self.load_default_context() | |
|
113 | ||
|
114 | c.active = 'exceptions' | |
|
115 | c.exception_id = self.request.matchdict['exception_id'] | |
|
116 | c.traceback = exc_tracking.read_exception(c.exception_id, prefix=None) | |
|
117 | return self._get_template_context(c) | |
|
118 | ||
|
119 | @LoginRequired() | |
|
120 | @HasPermissionAllDecorator('hg.admin') | |
|
121 | @CSRFRequired() | |
|
122 | @view_config( | |
|
123 | route_name='admin_settings_exception_tracker_delete_all', request_method='POST', | |
|
124 | renderer='rhodecode:templates/admin/settings/settings.mako') | |
|
125 | def exception_delete_all(self): | |
|
126 | _ = self.request.translate | |
|
127 | c = self.load_default_context() | |
|
128 | ||
|
129 | c.active = 'exceptions' | |
|
130 | all_exc = self.get_all_exceptions() | |
|
131 | exc_count = len(all_exc) | |
|
132 | for exc in all_exc: | |
|
133 | exc_tracking.delete_exception(exc['exc_id'], prefix=None) | |
|
134 | ||
|
135 | h.flash(_('Removed {} Exceptions').format(exc_count), category='success') | |
|
136 | raise HTTPFound(h.route_path('admin_settings_exception_tracker')) | |
|
137 | ||
|
138 | @LoginRequired() | |
|
139 | @HasPermissionAllDecorator('hg.admin') | |
|
140 | @CSRFRequired() | |
|
141 | @view_config( | |
|
142 | route_name='admin_settings_exception_tracker_delete', request_method='POST', | |
|
143 | renderer='rhodecode:templates/admin/settings/settings.mako') | |
|
144 | def exception_delete(self): | |
|
145 | _ = self.request.translate | |
|
146 | c = self.load_default_context() | |
|
147 | ||
|
148 | c.active = 'exceptions' | |
|
149 | c.exception_id = self.request.matchdict['exception_id'] | |
|
150 | exc_tracking.delete_exception(c.exception_id, prefix=None) | |
|
151 | ||
|
152 | h.flash(_('Removed Exception {}').format(c.exception_id), category='success') | |
|
153 | raise HTTPFound(h.route_path('admin_settings_exception_tracker')) |
@@ -0,0 +1,146 b'' | |||
|
1 | # -*- coding: utf-8 -*- | |
|
2 | ||
|
3 | # Copyright (C) 2010-2018 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 os | |
|
22 | import time | |
|
23 | import datetime | |
|
24 | import msgpack | |
|
25 | import logging | |
|
26 | import traceback | |
|
27 | import tempfile | |
|
28 | ||
|
29 | ||
|
30 | log = logging.getLogger(__name__) | |
|
31 | ||
|
32 | # NOTE: Any changes should be synced with exc_tracking at vcsserver.lib.exc_tracking | |
|
33 | global_prefix = 'rhodecode' | |
|
34 | ||
|
35 | ||
|
36 | def exc_serialize(exc_id, tb, exc_type): | |
|
37 | ||
|
38 | data = { | |
|
39 | 'version': 'v1', | |
|
40 | 'exc_id': exc_id, | |
|
41 | 'exc_utc_date': datetime.datetime.utcnow().isoformat(), | |
|
42 | 'exc_timestamp': repr(time.time()), | |
|
43 | 'exc_message': tb, | |
|
44 | 'exc_type': exc_type, | |
|
45 | } | |
|
46 | return msgpack.packb(data), data | |
|
47 | ||
|
48 | ||
|
49 | def exc_unserialize(tb): | |
|
50 | return msgpack.unpackb(tb) | |
|
51 | ||
|
52 | ||
|
53 | def get_exc_store(): | |
|
54 | """ | |
|
55 | Get and create exception store if it's not existing | |
|
56 | """ | |
|
57 | exc_store_dir = 'rc_exception_store_v1' | |
|
58 | # fallback | |
|
59 | _exc_store_path = os.path.join(tempfile.gettempdir(), exc_store_dir) | |
|
60 | ||
|
61 | exc_store_dir = '' # TODO: need a persistent cross instance store here | |
|
62 | if exc_store_dir: | |
|
63 | _exc_store_path = os.path.join(exc_store_dir, exc_store_dir) | |
|
64 | ||
|
65 | _exc_store_path = os.path.abspath(_exc_store_path) | |
|
66 | if not os.path.isdir(_exc_store_path): | |
|
67 | os.makedirs(_exc_store_path) | |
|
68 | log.debug('Initializing exceptions store at %s', _exc_store_path) | |
|
69 | return _exc_store_path | |
|
70 | ||
|
71 | ||
|
72 | def _store_exception(exc_id, exc_info, prefix): | |
|
73 | exc_type, exc_value, exc_traceback = exc_info | |
|
74 | tb = ''.join(traceback.format_exception( | |
|
75 | exc_type, exc_value, exc_traceback, None)) | |
|
76 | ||
|
77 | exc_type_name = exc_type.__name__ | |
|
78 | exc_store_path = get_exc_store() | |
|
79 | exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name) | |
|
80 | exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp']) | |
|
81 | if not os.path.isdir(exc_store_path): | |
|
82 | os.makedirs(exc_store_path) | |
|
83 | stored_exc_path = os.path.join(exc_store_path, exc_pref_id) | |
|
84 | with open(stored_exc_path, 'wb') as f: | |
|
85 | f.write(exc_data) | |
|
86 | log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path) | |
|
87 | ||
|
88 | ||
|
89 | def store_exception(exc_id, exc_info, prefix=global_prefix): | |
|
90 | try: | |
|
91 | _store_exception(exc_id=exc_id, exc_info=exc_info, prefix=prefix) | |
|
92 | except Exception: | |
|
93 | log.exception('Failed to store exception `%s` information', exc_id) | |
|
94 | # there's no way this can fail, it will crash server badly if it does. | |
|
95 | pass | |
|
96 | ||
|
97 | ||
|
98 | def _find_exc_file(exc_id, prefix=global_prefix): | |
|
99 | exc_store_path = get_exc_store() | |
|
100 | if prefix: | |
|
101 | exc_id = '{}_{}'.format(exc_id, prefix) | |
|
102 | else: | |
|
103 | # search without a prefix | |
|
104 | exc_id = '{}'.format(exc_id) | |
|
105 | ||
|
106 | # we need to search the store for such start pattern as above | |
|
107 | for fname in os.listdir(exc_store_path): | |
|
108 | if fname.startswith(exc_id): | |
|
109 | exc_id = os.path.join(exc_store_path, fname) | |
|
110 | break | |
|
111 | continue | |
|
112 | else: | |
|
113 | exc_id = None | |
|
114 | ||
|
115 | return exc_id | |
|
116 | ||
|
117 | ||
|
118 | def _read_exception(exc_id, prefix): | |
|
119 | exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix) | |
|
120 | if exc_id_file_path: | |
|
121 | with open(exc_id_file_path, 'rb') as f: | |
|
122 | return exc_unserialize(f.read()) | |
|
123 | else: | |
|
124 | log.debug('Exception File `%s` not found', exc_id_file_path) | |
|
125 | return None | |
|
126 | ||
|
127 | ||
|
128 | def read_exception(exc_id, prefix=global_prefix): | |
|
129 | try: | |
|
130 | return _read_exception(exc_id=exc_id, prefix=prefix) | |
|
131 | except Exception: | |
|
132 | log.exception('Failed to read exception `%s` information', exc_id) | |
|
133 | # there's no way this can fail, it will crash server badly if it does. | |
|
134 | return None | |
|
135 | ||
|
136 | ||
|
137 | def delete_exception(exc_id, prefix=global_prefix): | |
|
138 | try: | |
|
139 | exc_id_file_path = _find_exc_file(exc_id, prefix=prefix) | |
|
140 | if exc_id_file_path: | |
|
141 | os.remove(exc_id_file_path) | |
|
142 | ||
|
143 | except Exception: | |
|
144 | log.exception('Failed to remove exception `%s` information', exc_id) | |
|
145 | # there's no way this can fail, it will crash server badly if it does. | |
|
146 | pass |
@@ -0,0 +1,38 b'' | |||
|
1 | <div class="panel panel-default"> | |
|
2 | <div class="panel-heading"> | |
|
3 | <h3 class="panel-title">${_('Exceptions Tracker - Exception ID')}: ${c.exception_id}</h3> | |
|
4 | </div> | |
|
5 | <div class="panel-body"> | |
|
6 | % if c.traceback: | |
|
7 | ||
|
8 | <h4>${_('Exception `{}` generated on UTC date: {}').format(c.traceback.get('exc_type', 'NO_TYPE'), c.traceback.get('exc_utc_date', 'NO_DATE'))}</h4> | |
|
9 | <pre>${c.traceback.get('exc_message', 'NO_MESSAGE')}</pre> | |
|
10 | ||
|
11 | % else: | |
|
12 | ${_('Unable to Read Exception. It might be removed or non-existing.')} | |
|
13 | % endif | |
|
14 | </div> | |
|
15 | </div> | |
|
16 | ||
|
17 | ||
|
18 | % if c.traceback: | |
|
19 | <div class="panel panel-danger"> | |
|
20 | <div class="panel-heading" id="advanced-delete"> | |
|
21 | <h3 class="panel-title">${_('Delete this Exception')}</h3> | |
|
22 | </div> | |
|
23 | <div class="panel-body"> | |
|
24 | ${h.secure_form(h.route_path('admin_settings_exception_tracker_delete', exception_id=c.exception_id), request=request)} | |
|
25 | <div style="margin: 0 0 20px 0" class="fake-space"></div> | |
|
26 | ||
|
27 | <div class="field"> | |
|
28 | <button class="btn btn-small btn-danger" type="submit" | |
|
29 | onclick="return confirm('${_('Confirm to delete this exception')}');"> | |
|
30 | <i class="icon-remove-sign"></i> | |
|
31 | ${_('Delete This Exception')} | |
|
32 | </button> | |
|
33 | </div> | |
|
34 | ||
|
35 | ${h.end_form()} | |
|
36 | </div> | |
|
37 | </div> | |
|
38 | % endif |
@@ -0,0 +1,52 b'' | |||
|
1 | <div class="panel panel-default"> | |
|
2 | <div class="panel-heading"> | |
|
3 | <h3 class="panel-title">${_('Exceptions Tracker ')}</h3> | |
|
4 | </div> | |
|
5 | <div class="panel-body"> | |
|
6 | % if c.exception_list_count == 1: | |
|
7 | ${_('There is {} stored exception.').format(c.exception_list_count)} | |
|
8 | % else: | |
|
9 | ${_('There are {} stored exceptions.').format(c.exception_list_count)} | |
|
10 | % endif | |
|
11 | ${_('Store directory')}: ${c.exception_store_dir} | |
|
12 | ||
|
13 | ${h.secure_form(h.route_path('admin_settings_exception_tracker_delete_all'), request=request)} | |
|
14 | <div style="margin: 0 0 20px 0" class="fake-space"></div> | |
|
15 | ||
|
16 | <div class="field"> | |
|
17 | <button class="btn btn-small btn-danger" type="submit" | |
|
18 | onclick="return confirm('${_('Confirm to delete all exceptions')}');"> | |
|
19 | <i class="icon-remove-sign"></i> | |
|
20 | ${_('Delete All')} | |
|
21 | </button> | |
|
22 | </div> | |
|
23 | ||
|
24 | ${h.end_form()} | |
|
25 | ||
|
26 | </div> | |
|
27 | </div> | |
|
28 | ||
|
29 | ||
|
30 | <div class="panel panel-default"> | |
|
31 | <div class="panel-heading"> | |
|
32 | <h3 class="panel-title">${_('Exceptions Tracker - Showing the last {} Exceptions').format(c.limit)}</h3> | |
|
33 | </div> | |
|
34 | <div class="panel-body"> | |
|
35 | <table class="rctable"> | |
|
36 | <tr> | |
|
37 | <th>Exception ID</th> | |
|
38 | <th>Date</th> | |
|
39 | <th>App Type</th> | |
|
40 | <th>Exc Type</th> | |
|
41 | </tr> | |
|
42 | % for tb in c.exception_list: | |
|
43 | <tr> | |
|
44 | <td><a href="${h.route_path('admin_settings_exception_tracker_show', exception_id=tb['exc_id'])}"><code>${tb['exc_id']}</code></a></td> | |
|
45 | <td>${h.format_date(tb['exc_utc_date'])}</td> | |
|
46 | <td>${tb['app_type']}</td> | |
|
47 | <td>${tb['exc_type']}</td> | |
|
48 | </tr> | |
|
49 | % endfor | |
|
50 | </table> | |
|
51 | </div> | |
|
52 | </div> |
@@ -1,426 +1,439 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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.apps._base import ADMIN_PREFIX |
|
23 | 23 | |
|
24 | 24 | |
|
25 | 25 | def admin_routes(config): |
|
26 | 26 | """ |
|
27 | 27 | Admin prefixed routes |
|
28 | 28 | """ |
|
29 | 29 | |
|
30 | 30 | config.add_route( |
|
31 | 31 | name='admin_audit_logs', |
|
32 | 32 | pattern='/audit_logs') |
|
33 | 33 | |
|
34 | 34 | config.add_route( |
|
35 | 35 | name='admin_audit_log_entry', |
|
36 | 36 | pattern='/audit_logs/{audit_log_id}') |
|
37 | 37 | |
|
38 | 38 | config.add_route( |
|
39 | 39 | name='pull_requests_global_0', # backward compat |
|
40 | 40 | pattern='/pull_requests/{pull_request_id:\d+}') |
|
41 | 41 | config.add_route( |
|
42 | 42 | name='pull_requests_global_1', # backward compat |
|
43 | 43 | pattern='/pull-requests/{pull_request_id:\d+}') |
|
44 | 44 | config.add_route( |
|
45 | 45 | name='pull_requests_global', |
|
46 | 46 | pattern='/pull-request/{pull_request_id:\d+}') |
|
47 | 47 | |
|
48 | 48 | config.add_route( |
|
49 | 49 | name='admin_settings_open_source', |
|
50 | 50 | pattern='/settings/open_source') |
|
51 | 51 | config.add_route( |
|
52 | 52 | name='admin_settings_vcs_svn_generate_cfg', |
|
53 | 53 | pattern='/settings/vcs/svn_generate_cfg') |
|
54 | 54 | |
|
55 | 55 | config.add_route( |
|
56 | 56 | name='admin_settings_system', |
|
57 | 57 | pattern='/settings/system') |
|
58 | 58 | config.add_route( |
|
59 | 59 | name='admin_settings_system_update', |
|
60 | 60 | pattern='/settings/system/updates') |
|
61 | 61 | |
|
62 | 62 | config.add_route( |
|
63 | name='admin_settings_exception_tracker', | |
|
64 | pattern='/settings/exceptions') | |
|
65 | config.add_route( | |
|
66 | name='admin_settings_exception_tracker_delete_all', | |
|
67 | pattern='/settings/exceptions/delete') | |
|
68 | config.add_route( | |
|
69 | name='admin_settings_exception_tracker_show', | |
|
70 | pattern='/settings/exceptions/{exception_id}') | |
|
71 | config.add_route( | |
|
72 | name='admin_settings_exception_tracker_delete', | |
|
73 | pattern='/settings/exceptions/{exception_id}/delete') | |
|
74 | ||
|
75 | config.add_route( | |
|
63 | 76 | name='admin_settings_sessions', |
|
64 | 77 | pattern='/settings/sessions') |
|
65 | 78 | config.add_route( |
|
66 | 79 | name='admin_settings_sessions_cleanup', |
|
67 | 80 | pattern='/settings/sessions/cleanup') |
|
68 | 81 | |
|
69 | 82 | config.add_route( |
|
70 | 83 | name='admin_settings_process_management', |
|
71 | 84 | pattern='/settings/process_management') |
|
72 | 85 | config.add_route( |
|
73 | 86 | name='admin_settings_process_management_data', |
|
74 | 87 | pattern='/settings/process_management/data') |
|
75 | 88 | config.add_route( |
|
76 | 89 | name='admin_settings_process_management_signal', |
|
77 | 90 | pattern='/settings/process_management/signal') |
|
78 | 91 | config.add_route( |
|
79 | 92 | name='admin_settings_process_management_master_signal', |
|
80 | 93 | pattern='/settings/process_management/master_signal') |
|
81 | 94 | |
|
82 | 95 | # default settings |
|
83 | 96 | config.add_route( |
|
84 | 97 | name='admin_defaults_repositories', |
|
85 | 98 | pattern='/defaults/repositories') |
|
86 | 99 | config.add_route( |
|
87 | 100 | name='admin_defaults_repositories_update', |
|
88 | 101 | pattern='/defaults/repositories/update') |
|
89 | 102 | |
|
90 | 103 | # admin settings |
|
91 | 104 | |
|
92 | 105 | config.add_route( |
|
93 | 106 | name='admin_settings', |
|
94 | 107 | pattern='/settings') |
|
95 | 108 | config.add_route( |
|
96 | 109 | name='admin_settings_update', |
|
97 | 110 | pattern='/settings/update') |
|
98 | 111 | |
|
99 | 112 | config.add_route( |
|
100 | 113 | name='admin_settings_global', |
|
101 | 114 | pattern='/settings/global') |
|
102 | 115 | config.add_route( |
|
103 | 116 | name='admin_settings_global_update', |
|
104 | 117 | pattern='/settings/global/update') |
|
105 | 118 | |
|
106 | 119 | config.add_route( |
|
107 | 120 | name='admin_settings_vcs', |
|
108 | 121 | pattern='/settings/vcs') |
|
109 | 122 | config.add_route( |
|
110 | 123 | name='admin_settings_vcs_update', |
|
111 | 124 | pattern='/settings/vcs/update') |
|
112 | 125 | config.add_route( |
|
113 | 126 | name='admin_settings_vcs_svn_pattern_delete', |
|
114 | 127 | pattern='/settings/vcs/svn_pattern_delete') |
|
115 | 128 | |
|
116 | 129 | config.add_route( |
|
117 | 130 | name='admin_settings_mapping', |
|
118 | 131 | pattern='/settings/mapping') |
|
119 | 132 | config.add_route( |
|
120 | 133 | name='admin_settings_mapping_update', |
|
121 | 134 | pattern='/settings/mapping/update') |
|
122 | 135 | |
|
123 | 136 | config.add_route( |
|
124 | 137 | name='admin_settings_visual', |
|
125 | 138 | pattern='/settings/visual') |
|
126 | 139 | config.add_route( |
|
127 | 140 | name='admin_settings_visual_update', |
|
128 | 141 | pattern='/settings/visual/update') |
|
129 | 142 | |
|
130 | 143 | |
|
131 | 144 | config.add_route( |
|
132 | 145 | name='admin_settings_issuetracker', |
|
133 | 146 | pattern='/settings/issue-tracker') |
|
134 | 147 | config.add_route( |
|
135 | 148 | name='admin_settings_issuetracker_update', |
|
136 | 149 | pattern='/settings/issue-tracker/update') |
|
137 | 150 | config.add_route( |
|
138 | 151 | name='admin_settings_issuetracker_test', |
|
139 | 152 | pattern='/settings/issue-tracker/test') |
|
140 | 153 | config.add_route( |
|
141 | 154 | name='admin_settings_issuetracker_delete', |
|
142 | 155 | pattern='/settings/issue-tracker/delete') |
|
143 | 156 | |
|
144 | 157 | config.add_route( |
|
145 | 158 | name='admin_settings_email', |
|
146 | 159 | pattern='/settings/email') |
|
147 | 160 | config.add_route( |
|
148 | 161 | name='admin_settings_email_update', |
|
149 | 162 | pattern='/settings/email/update') |
|
150 | 163 | |
|
151 | 164 | config.add_route( |
|
152 | 165 | name='admin_settings_hooks', |
|
153 | 166 | pattern='/settings/hooks') |
|
154 | 167 | config.add_route( |
|
155 | 168 | name='admin_settings_hooks_update', |
|
156 | 169 | pattern='/settings/hooks/update') |
|
157 | 170 | config.add_route( |
|
158 | 171 | name='admin_settings_hooks_delete', |
|
159 | 172 | pattern='/settings/hooks/delete') |
|
160 | 173 | |
|
161 | 174 | config.add_route( |
|
162 | 175 | name='admin_settings_search', |
|
163 | 176 | pattern='/settings/search') |
|
164 | 177 | |
|
165 | 178 | config.add_route( |
|
166 | 179 | name='admin_settings_labs', |
|
167 | 180 | pattern='/settings/labs') |
|
168 | 181 | config.add_route( |
|
169 | 182 | name='admin_settings_labs_update', |
|
170 | 183 | pattern='/settings/labs/update') |
|
171 | 184 | |
|
172 | 185 | # Automation EE feature |
|
173 | 186 | config.add_route( |
|
174 | 187 | 'admin_settings_automation', |
|
175 | 188 | pattern=ADMIN_PREFIX + '/settings/automation') |
|
176 | 189 | |
|
177 | 190 | # global permissions |
|
178 | 191 | |
|
179 | 192 | config.add_route( |
|
180 | 193 | name='admin_permissions_application', |
|
181 | 194 | pattern='/permissions/application') |
|
182 | 195 | config.add_route( |
|
183 | 196 | name='admin_permissions_application_update', |
|
184 | 197 | pattern='/permissions/application/update') |
|
185 | 198 | |
|
186 | 199 | config.add_route( |
|
187 | 200 | name='admin_permissions_global', |
|
188 | 201 | pattern='/permissions/global') |
|
189 | 202 | config.add_route( |
|
190 | 203 | name='admin_permissions_global_update', |
|
191 | 204 | pattern='/permissions/global/update') |
|
192 | 205 | |
|
193 | 206 | config.add_route( |
|
194 | 207 | name='admin_permissions_object', |
|
195 | 208 | pattern='/permissions/object') |
|
196 | 209 | config.add_route( |
|
197 | 210 | name='admin_permissions_object_update', |
|
198 | 211 | pattern='/permissions/object/update') |
|
199 | 212 | |
|
200 | 213 | config.add_route( |
|
201 | 214 | name='admin_permissions_ips', |
|
202 | 215 | pattern='/permissions/ips') |
|
203 | 216 | |
|
204 | 217 | config.add_route( |
|
205 | 218 | name='admin_permissions_overview', |
|
206 | 219 | pattern='/permissions/overview') |
|
207 | 220 | |
|
208 | 221 | config.add_route( |
|
209 | 222 | name='admin_permissions_auth_token_access', |
|
210 | 223 | pattern='/permissions/auth_token_access') |
|
211 | 224 | |
|
212 | 225 | config.add_route( |
|
213 | 226 | name='admin_permissions_ssh_keys', |
|
214 | 227 | pattern='/permissions/ssh_keys') |
|
215 | 228 | config.add_route( |
|
216 | 229 | name='admin_permissions_ssh_keys_data', |
|
217 | 230 | pattern='/permissions/ssh_keys/data') |
|
218 | 231 | config.add_route( |
|
219 | 232 | name='admin_permissions_ssh_keys_update', |
|
220 | 233 | pattern='/permissions/ssh_keys/update') |
|
221 | 234 | |
|
222 | 235 | # users admin |
|
223 | 236 | config.add_route( |
|
224 | 237 | name='users', |
|
225 | 238 | pattern='/users') |
|
226 | 239 | |
|
227 | 240 | config.add_route( |
|
228 | 241 | name='users_data', |
|
229 | 242 | pattern='/users_data') |
|
230 | 243 | |
|
231 | 244 | config.add_route( |
|
232 | 245 | name='users_create', |
|
233 | 246 | pattern='/users/create') |
|
234 | 247 | |
|
235 | 248 | config.add_route( |
|
236 | 249 | name='users_new', |
|
237 | 250 | pattern='/users/new') |
|
238 | 251 | |
|
239 | 252 | # user management |
|
240 | 253 | config.add_route( |
|
241 | 254 | name='user_edit', |
|
242 | 255 | pattern='/users/{user_id:\d+}/edit', |
|
243 | 256 | user_route=True) |
|
244 | 257 | config.add_route( |
|
245 | 258 | name='user_edit_advanced', |
|
246 | 259 | pattern='/users/{user_id:\d+}/edit/advanced', |
|
247 | 260 | user_route=True) |
|
248 | 261 | config.add_route( |
|
249 | 262 | name='user_edit_global_perms', |
|
250 | 263 | pattern='/users/{user_id:\d+}/edit/global_permissions', |
|
251 | 264 | user_route=True) |
|
252 | 265 | config.add_route( |
|
253 | 266 | name='user_edit_global_perms_update', |
|
254 | 267 | pattern='/users/{user_id:\d+}/edit/global_permissions/update', |
|
255 | 268 | user_route=True) |
|
256 | 269 | config.add_route( |
|
257 | 270 | name='user_update', |
|
258 | 271 | pattern='/users/{user_id:\d+}/update', |
|
259 | 272 | user_route=True) |
|
260 | 273 | config.add_route( |
|
261 | 274 | name='user_delete', |
|
262 | 275 | pattern='/users/{user_id:\d+}/delete', |
|
263 | 276 | user_route=True) |
|
264 | 277 | config.add_route( |
|
265 | 278 | name='user_force_password_reset', |
|
266 | 279 | pattern='/users/{user_id:\d+}/password_reset', |
|
267 | 280 | user_route=True) |
|
268 | 281 | config.add_route( |
|
269 | 282 | name='user_create_personal_repo_group', |
|
270 | 283 | pattern='/users/{user_id:\d+}/create_repo_group', |
|
271 | 284 | user_route=True) |
|
272 | 285 | |
|
273 | 286 | # user auth tokens |
|
274 | 287 | config.add_route( |
|
275 | 288 | name='edit_user_auth_tokens', |
|
276 | 289 | pattern='/users/{user_id:\d+}/edit/auth_tokens', |
|
277 | 290 | user_route=True) |
|
278 | 291 | config.add_route( |
|
279 | 292 | name='edit_user_auth_tokens_add', |
|
280 | 293 | pattern='/users/{user_id:\d+}/edit/auth_tokens/new', |
|
281 | 294 | user_route=True) |
|
282 | 295 | config.add_route( |
|
283 | 296 | name='edit_user_auth_tokens_delete', |
|
284 | 297 | pattern='/users/{user_id:\d+}/edit/auth_tokens/delete', |
|
285 | 298 | user_route=True) |
|
286 | 299 | |
|
287 | 300 | # user ssh keys |
|
288 | 301 | config.add_route( |
|
289 | 302 | name='edit_user_ssh_keys', |
|
290 | 303 | pattern='/users/{user_id:\d+}/edit/ssh_keys', |
|
291 | 304 | user_route=True) |
|
292 | 305 | config.add_route( |
|
293 | 306 | name='edit_user_ssh_keys_generate_keypair', |
|
294 | 307 | pattern='/users/{user_id:\d+}/edit/ssh_keys/generate', |
|
295 | 308 | user_route=True) |
|
296 | 309 | config.add_route( |
|
297 | 310 | name='edit_user_ssh_keys_add', |
|
298 | 311 | pattern='/users/{user_id:\d+}/edit/ssh_keys/new', |
|
299 | 312 | user_route=True) |
|
300 | 313 | config.add_route( |
|
301 | 314 | name='edit_user_ssh_keys_delete', |
|
302 | 315 | pattern='/users/{user_id:\d+}/edit/ssh_keys/delete', |
|
303 | 316 | user_route=True) |
|
304 | 317 | |
|
305 | 318 | # user emails |
|
306 | 319 | config.add_route( |
|
307 | 320 | name='edit_user_emails', |
|
308 | 321 | pattern='/users/{user_id:\d+}/edit/emails', |
|
309 | 322 | user_route=True) |
|
310 | 323 | config.add_route( |
|
311 | 324 | name='edit_user_emails_add', |
|
312 | 325 | pattern='/users/{user_id:\d+}/edit/emails/new', |
|
313 | 326 | user_route=True) |
|
314 | 327 | config.add_route( |
|
315 | 328 | name='edit_user_emails_delete', |
|
316 | 329 | pattern='/users/{user_id:\d+}/edit/emails/delete', |
|
317 | 330 | user_route=True) |
|
318 | 331 | |
|
319 | 332 | # user IPs |
|
320 | 333 | config.add_route( |
|
321 | 334 | name='edit_user_ips', |
|
322 | 335 | pattern='/users/{user_id:\d+}/edit/ips', |
|
323 | 336 | user_route=True) |
|
324 | 337 | config.add_route( |
|
325 | 338 | name='edit_user_ips_add', |
|
326 | 339 | pattern='/users/{user_id:\d+}/edit/ips/new', |
|
327 | 340 | user_route_with_default=True) # enabled for default user too |
|
328 | 341 | config.add_route( |
|
329 | 342 | name='edit_user_ips_delete', |
|
330 | 343 | pattern='/users/{user_id:\d+}/edit/ips/delete', |
|
331 | 344 | user_route_with_default=True) # enabled for default user too |
|
332 | 345 | |
|
333 | 346 | # user perms |
|
334 | 347 | config.add_route( |
|
335 | 348 | name='edit_user_perms_summary', |
|
336 | 349 | pattern='/users/{user_id:\d+}/edit/permissions_summary', |
|
337 | 350 | user_route=True) |
|
338 | 351 | config.add_route( |
|
339 | 352 | name='edit_user_perms_summary_json', |
|
340 | 353 | pattern='/users/{user_id:\d+}/edit/permissions_summary/json', |
|
341 | 354 | user_route=True) |
|
342 | 355 | |
|
343 | 356 | # user user groups management |
|
344 | 357 | config.add_route( |
|
345 | 358 | name='edit_user_groups_management', |
|
346 | 359 | pattern='/users/{user_id:\d+}/edit/groups_management', |
|
347 | 360 | user_route=True) |
|
348 | 361 | |
|
349 | 362 | config.add_route( |
|
350 | 363 | name='edit_user_groups_management_updates', |
|
351 | 364 | pattern='/users/{user_id:\d+}/edit/edit_user_groups_management/updates', |
|
352 | 365 | user_route=True) |
|
353 | 366 | |
|
354 | 367 | # user audit logs |
|
355 | 368 | config.add_route( |
|
356 | 369 | name='edit_user_audit_logs', |
|
357 | 370 | pattern='/users/{user_id:\d+}/edit/audit', user_route=True) |
|
358 | 371 | |
|
359 | 372 | # user caches |
|
360 | 373 | config.add_route( |
|
361 | 374 | name='edit_user_caches', |
|
362 | 375 | pattern='/users/{user_id:\d+}/edit/caches', |
|
363 | 376 | user_route=True) |
|
364 | 377 | config.add_route( |
|
365 | 378 | name='edit_user_caches_update', |
|
366 | 379 | pattern='/users/{user_id:\d+}/edit/caches/update', |
|
367 | 380 | user_route=True) |
|
368 | 381 | |
|
369 | 382 | # user-groups admin |
|
370 | 383 | config.add_route( |
|
371 | 384 | name='user_groups', |
|
372 | 385 | pattern='/user_groups') |
|
373 | 386 | |
|
374 | 387 | config.add_route( |
|
375 | 388 | name='user_groups_data', |
|
376 | 389 | pattern='/user_groups_data') |
|
377 | 390 | |
|
378 | 391 | config.add_route( |
|
379 | 392 | name='user_groups_new', |
|
380 | 393 | pattern='/user_groups/new') |
|
381 | 394 | |
|
382 | 395 | config.add_route( |
|
383 | 396 | name='user_groups_create', |
|
384 | 397 | pattern='/user_groups/create') |
|
385 | 398 | |
|
386 | 399 | # repos admin |
|
387 | 400 | config.add_route( |
|
388 | 401 | name='repos', |
|
389 | 402 | pattern='/repos') |
|
390 | 403 | |
|
391 | 404 | config.add_route( |
|
392 | 405 | name='repo_new', |
|
393 | 406 | pattern='/repos/new') |
|
394 | 407 | |
|
395 | 408 | config.add_route( |
|
396 | 409 | name='repo_create', |
|
397 | 410 | pattern='/repos/create') |
|
398 | 411 | |
|
399 | 412 | # repo groups admin |
|
400 | 413 | config.add_route( |
|
401 | 414 | name='repo_groups', |
|
402 | 415 | pattern='/repo_groups') |
|
403 | 416 | |
|
404 | 417 | config.add_route( |
|
405 | 418 | name='repo_group_new', |
|
406 | 419 | pattern='/repo_group/new') |
|
407 | 420 | |
|
408 | 421 | config.add_route( |
|
409 | 422 | name='repo_group_create', |
|
410 | 423 | pattern='/repo_group/create') |
|
411 | 424 | |
|
412 | 425 | |
|
413 | 426 | def includeme(config): |
|
414 | 427 | from rhodecode.apps.admin.navigation import includeme as nav_includeme |
|
415 | 428 | |
|
416 | 429 | # Create admin navigation registry and add it to the pyramid registry. |
|
417 | 430 | nav_includeme(config) |
|
418 | 431 | |
|
419 | 432 | # main admin routes |
|
420 | 433 | config.add_route(name='admin_home', pattern=ADMIN_PREFIX) |
|
421 | 434 | config.include(admin_routes, route_prefix=ADMIN_PREFIX) |
|
422 | 435 | |
|
423 | 436 | config.include('.subscribers') |
|
424 | 437 | |
|
425 | 438 | # Scan module for configuration decorators. |
|
426 | 439 | config.scan('.views', ignore='.tests') |
@@ -1,144 +1,147 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2016-2018 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 zope.interface import implementer |
|
26 | 26 | |
|
27 | 27 | from rhodecode.apps.admin.interfaces import IAdminNavigationRegistry |
|
28 | 28 | from rhodecode.lib.utils2 import str2bool |
|
29 | 29 | from rhodecode.translation import _ |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | log = logging.getLogger(__name__) |
|
33 | 33 | |
|
34 | 34 | NavListEntry = collections.namedtuple( |
|
35 | 35 | 'NavListEntry', ['key', 'name', 'url', 'active_list']) |
|
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 active_list: list of urls that we select active for this element |
|
46 | 46 | """ |
|
47 | 47 | |
|
48 | 48 | def __init__(self, key, name, view_name, active_list=None): |
|
49 | 49 | self.key = key |
|
50 | 50 | self.name = name |
|
51 | 51 | self.view_name = view_name |
|
52 | 52 | self._active_list = active_list or [] |
|
53 | 53 | |
|
54 | 54 | def generate_url(self, request): |
|
55 | 55 | return request.route_path(self.view_name) |
|
56 | 56 | |
|
57 | 57 | def get_localized_name(self, request): |
|
58 | 58 | return request.translate(self.name) |
|
59 | 59 | |
|
60 | 60 | @property |
|
61 | 61 | def active_list(self): |
|
62 | 62 | active_list = [self.key] |
|
63 | 63 | if self._active_list: |
|
64 | 64 | active_list = self._active_list |
|
65 | 65 | return active_list |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | @implementer(IAdminNavigationRegistry) |
|
69 | 69 | class NavigationRegistry(object): |
|
70 | 70 | |
|
71 | 71 | _base_entries = [ |
|
72 | 72 | NavEntry('global', _('Global'), |
|
73 | 73 | 'admin_settings_global'), |
|
74 | 74 | NavEntry('vcs', _('VCS'), |
|
75 | 75 | 'admin_settings_vcs'), |
|
76 | 76 | NavEntry('visual', _('Visual'), |
|
77 | 77 | 'admin_settings_visual'), |
|
78 | 78 | NavEntry('mapping', _('Remap and Rescan'), |
|
79 | 79 | 'admin_settings_mapping'), |
|
80 | 80 | NavEntry('issuetracker', _('Issue Tracker'), |
|
81 | 81 | 'admin_settings_issuetracker'), |
|
82 | 82 | NavEntry('email', _('Email'), |
|
83 | 83 | 'admin_settings_email'), |
|
84 | 84 | NavEntry('hooks', _('Hooks'), |
|
85 | 85 | 'admin_settings_hooks'), |
|
86 | 86 | NavEntry('search', _('Full Text Search'), |
|
87 | 87 | 'admin_settings_search'), |
|
88 | 88 | NavEntry('integrations', _('Integrations'), |
|
89 | 89 | 'global_integrations_home'), |
|
90 | 90 | NavEntry('system', _('System Info'), |
|
91 | 91 | 'admin_settings_system'), |
|
92 | NavEntry('exceptions', _('Exceptions Tracker'), | |
|
93 | 'admin_settings_exception_tracker', | |
|
94 | active_list=['exceptions', 'exceptions_browse']), | |
|
92 | 95 | NavEntry('process_management', _('Processes'), |
|
93 | 96 | 'admin_settings_process_management'), |
|
94 | 97 | NavEntry('sessions', _('User Sessions'), |
|
95 | 98 | 'admin_settings_sessions'), |
|
96 | 99 | NavEntry('open_source', _('Open Source Licenses'), |
|
97 | 100 | 'admin_settings_open_source'), |
|
98 | 101 | NavEntry('automation', _('Automation'), |
|
99 | 102 | 'admin_settings_automation') |
|
100 | 103 | ] |
|
101 | 104 | |
|
102 | 105 | _labs_entry = NavEntry('labs', _('Labs'), |
|
103 | 106 | 'admin_settings_labs') |
|
104 | 107 | |
|
105 | 108 | def __init__(self, labs_active=False): |
|
106 | 109 | self._registered_entries = collections.OrderedDict() |
|
107 | 110 | for item in self.__class__._base_entries: |
|
108 | 111 | self._registered_entries[item.key] = item |
|
109 | 112 | |
|
110 | 113 | if labs_active: |
|
111 | 114 | self.add_entry(self._labs_entry) |
|
112 | 115 | |
|
113 | 116 | def add_entry(self, entry): |
|
114 | 117 | self._registered_entries[entry.key] = entry |
|
115 | 118 | |
|
116 | 119 | def get_navlist(self, request): |
|
117 | 120 | navlist = [NavListEntry(i.key, i.get_localized_name(request), |
|
118 | 121 | i.generate_url(request), i.active_list) |
|
119 | 122 | for i in self._registered_entries.values()] |
|
120 | 123 | return navlist |
|
121 | 124 | |
|
122 | 125 | |
|
123 | 126 | def navigation_registry(request, registry=None): |
|
124 | 127 | """ |
|
125 | 128 | Helper that returns the admin navigation registry. |
|
126 | 129 | """ |
|
127 | 130 | pyramid_registry = registry or request.registry |
|
128 | 131 | nav_registry = pyramid_registry.queryUtility(IAdminNavigationRegistry) |
|
129 | 132 | return nav_registry |
|
130 | 133 | |
|
131 | 134 | |
|
132 | 135 | def navigation_list(request): |
|
133 | 136 | """ |
|
134 | 137 | Helper that returns the admin navigation as list of NavListEntry objects. |
|
135 | 138 | """ |
|
136 | 139 | return navigation_registry(request).get_navlist(request) |
|
137 | 140 | |
|
138 | 141 | |
|
139 | 142 | def includeme(config): |
|
140 | 143 | # Create admin navigation registry and add it to the pyramid registry. |
|
141 | 144 | settings = config.get_settings() |
|
142 | 145 | labs_active = str2bool(settings.get('labs_settings_active', False)) |
|
143 | 146 | navigation_registry = NavigationRegistry(labs_active=labs_active) |
|
144 | 147 | config.registry.registerUtility(navigation_registry) No newline at end of file |
@@ -1,510 +1,522 b'' | |||
|
1 | 1 | # -*- coding: utf-8 -*- |
|
2 | 2 | |
|
3 | 3 | # Copyright (C) 2010-2018 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 os |
|
22 | import sys | |
|
22 | 23 | import logging |
|
23 | 24 | import traceback |
|
24 | 25 | import collections |
|
25 | 26 | import tempfile |
|
26 | 27 | |
|
27 | 28 | from paste.gzipper import make_gzip_middleware |
|
28 | 29 | from pyramid.wsgi import wsgiapp |
|
29 | 30 | from pyramid.authorization import ACLAuthorizationPolicy |
|
30 | 31 | from pyramid.config import Configurator |
|
31 | 32 | from pyramid.settings import asbool, aslist |
|
32 | 33 | from pyramid.httpexceptions import ( |
|
33 | 34 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound) |
|
34 | 35 | from pyramid.events import ApplicationCreated |
|
35 | 36 | from pyramid.renderers import render_to_response |
|
36 | 37 | |
|
37 | 38 | from rhodecode.model import meta |
|
38 | 39 | from rhodecode.config import patches |
|
39 | 40 | from rhodecode.config import utils as config_utils |
|
40 | 41 | from rhodecode.config.environment import load_pyramid_environment |
|
41 | 42 | |
|
42 | 43 | from rhodecode.lib.middleware.vcs import VCSMiddleware |
|
43 | 44 | from rhodecode.lib.request import Request |
|
44 | 45 | from rhodecode.lib.vcs import VCSCommunicationError |
|
45 | 46 | from rhodecode.lib.exceptions import VCSServerUnavailable |
|
46 | 47 | from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled |
|
47 | 48 | from rhodecode.lib.middleware.https_fixup import HttpsFixup |
|
48 | 49 | from rhodecode.lib.celerylib.loader import configure_celery |
|
49 | 50 | from rhodecode.lib.plugins.utils import register_rhodecode_plugin |
|
50 | 51 | from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict |
|
52 | from rhodecode.lib.exc_tracking import store_exception | |
|
51 | 53 | from rhodecode.subscribers import ( |
|
52 | 54 | scan_repositories_if_enabled, write_js_routes_if_enabled, |
|
53 | 55 | write_metadata_if_needed, inject_app_settings) |
|
54 | 56 | |
|
55 | 57 | |
|
56 | 58 | log = logging.getLogger(__name__) |
|
57 | 59 | |
|
58 | 60 | |
|
59 | 61 | def is_http_error(response): |
|
60 | 62 | # error which should have traceback |
|
61 | 63 | return response.status_code > 499 |
|
62 | 64 | |
|
63 | 65 | |
|
64 | 66 | def make_pyramid_app(global_config, **settings): |
|
65 | 67 | """ |
|
66 | 68 | Constructs the WSGI application based on Pyramid. |
|
67 | 69 | |
|
68 | 70 | Specials: |
|
69 | 71 | |
|
70 | 72 | * The application can also be integrated like a plugin via the call to |
|
71 | 73 | `includeme`. This is accompanied with the other utility functions which |
|
72 | 74 | are called. Changing this should be done with great care to not break |
|
73 | 75 | cases when these fragments are assembled from another place. |
|
74 | 76 | |
|
75 | 77 | """ |
|
76 | 78 | |
|
77 | 79 | # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It |
|
78 | 80 | # will be replaced by the value of the environment variable "NAME" in this case. |
|
79 | 81 | environ = { |
|
80 | 82 | 'ENV_{}'.format(key): value for key, value in os.environ.items()} |
|
81 | 83 | |
|
82 | 84 | global_config = _substitute_values(global_config, environ) |
|
83 | 85 | settings = _substitute_values(settings, environ) |
|
84 | 86 | |
|
85 | 87 | sanitize_settings_and_apply_defaults(settings) |
|
86 | 88 | |
|
87 | 89 | config = Configurator(settings=settings) |
|
88 | 90 | |
|
89 | 91 | # Apply compatibility patches |
|
90 | 92 | patches.inspect_getargspec() |
|
91 | 93 | |
|
92 | 94 | load_pyramid_environment(global_config, settings) |
|
93 | 95 | |
|
94 | 96 | # Static file view comes first |
|
95 | 97 | includeme_first(config) |
|
96 | 98 | |
|
97 | 99 | includeme(config) |
|
98 | 100 | |
|
99 | 101 | pyramid_app = config.make_wsgi_app() |
|
100 | 102 | pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config) |
|
101 | 103 | pyramid_app.config = config |
|
102 | 104 | |
|
103 | 105 | config.configure_celery(global_config['__file__']) |
|
104 | 106 | # creating the app uses a connection - return it after we are done |
|
105 | 107 | meta.Session.remove() |
|
106 | 108 | |
|
107 | 109 | log.info('Pyramid app %s created and configured.', pyramid_app) |
|
108 | 110 | return pyramid_app |
|
109 | 111 | |
|
110 | 112 | |
|
111 | 113 | def not_found_view(request): |
|
112 | 114 | """ |
|
113 | 115 | This creates the view which should be registered as not-found-view to |
|
114 | 116 | pyramid. |
|
115 | 117 | """ |
|
116 | 118 | |
|
117 | 119 | if not getattr(request, 'vcs_call', None): |
|
118 | 120 | # handle like regular case with our error_handler |
|
119 | 121 | return error_handler(HTTPNotFound(), request) |
|
120 | 122 | |
|
121 | 123 | # handle not found view as a vcs call |
|
122 | 124 | settings = request.registry.settings |
|
123 | 125 | ae_client = getattr(request, 'ae_client', None) |
|
124 | 126 | vcs_app = VCSMiddleware( |
|
125 | 127 | HTTPNotFound(), request.registry, settings, |
|
126 | 128 | appenlight_client=ae_client) |
|
127 | 129 | |
|
128 | 130 | return wsgiapp(vcs_app)(None, request) |
|
129 | 131 | |
|
130 | 132 | |
|
131 | 133 | def error_handler(exception, request): |
|
132 | 134 | import rhodecode |
|
133 | 135 | from rhodecode.lib import helpers |
|
134 | 136 | |
|
135 | 137 | rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode' |
|
136 | 138 | |
|
137 | 139 | base_response = HTTPInternalServerError() |
|
138 | 140 | # prefer original exception for the response since it may have headers set |
|
139 | 141 | if isinstance(exception, HTTPException): |
|
140 | 142 | base_response = exception |
|
141 | 143 | elif isinstance(exception, VCSCommunicationError): |
|
142 | 144 | base_response = VCSServerUnavailable() |
|
143 | 145 | |
|
144 | 146 | if is_http_error(base_response): |
|
145 | 147 | log.exception( |
|
146 | 148 | 'error occurred handling this request for path: %s', request.path) |
|
147 | 149 | |
|
148 | 150 | error_explanation = base_response.explanation or str(base_response) |
|
149 | 151 | if base_response.status_code == 404: |
|
150 | 152 | error_explanation += " Or you don't have permission to access it." |
|
151 | 153 | c = AttributeDict() |
|
152 | 154 | c.error_message = base_response.status |
|
153 | 155 | c.error_explanation = error_explanation |
|
154 | 156 | c.visual = AttributeDict() |
|
155 | 157 | |
|
156 | 158 | c.visual.rhodecode_support_url = ( |
|
157 | 159 | request.registry.settings.get('rhodecode_support_url') or |
|
158 | 160 | request.route_url('rhodecode_support') |
|
159 | 161 | ) |
|
160 | 162 | c.redirect_time = 0 |
|
161 | 163 | c.rhodecode_name = rhodecode_title |
|
162 | 164 | if not c.rhodecode_name: |
|
163 | 165 | c.rhodecode_name = 'Rhodecode' |
|
164 | 166 | |
|
165 | 167 | c.causes = [] |
|
166 | 168 | if is_http_error(base_response): |
|
167 | 169 | c.causes.append('Server is overloaded.') |
|
168 | 170 | c.causes.append('Server database connection is lost.') |
|
169 | 171 | c.causes.append('Server expected unhandled error.') |
|
170 | 172 | |
|
171 | 173 | if hasattr(base_response, 'causes'): |
|
172 | 174 | c.causes = base_response.causes |
|
173 | 175 | |
|
174 | 176 | c.messages = helpers.flash.pop_messages(request=request) |
|
175 | c.traceback = traceback.format_exc() | |
|
177 | ||
|
178 | exc_info = sys.exc_info() | |
|
179 | c.exception_id = id(exc_info) | |
|
180 | c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \ | |
|
181 | or base_response.status_code > 499 | |
|
182 | c.exception_id_url = request.route_url( | |
|
183 | 'admin_settings_exception_tracker_show', exception_id=c.exception_id) | |
|
184 | ||
|
185 | if c.show_exception_id: | |
|
186 | store_exception(c.exception_id, exc_info) | |
|
187 | ||
|
176 | 188 | response = render_to_response( |
|
177 | 189 | '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request, |
|
178 | 190 | response=base_response) |
|
179 | 191 | |
|
180 | 192 | return response |
|
181 | 193 | |
|
182 | 194 | |
|
183 | 195 | def includeme_first(config): |
|
184 | 196 | # redirect automatic browser favicon.ico requests to correct place |
|
185 | 197 | def favicon_redirect(context, request): |
|
186 | 198 | return HTTPFound( |
|
187 | 199 | request.static_path('rhodecode:public/images/favicon.ico')) |
|
188 | 200 | |
|
189 | 201 | config.add_view(favicon_redirect, route_name='favicon') |
|
190 | 202 | config.add_route('favicon', '/favicon.ico') |
|
191 | 203 | |
|
192 | 204 | def robots_redirect(context, request): |
|
193 | 205 | return HTTPFound( |
|
194 | 206 | request.static_path('rhodecode:public/robots.txt')) |
|
195 | 207 | |
|
196 | 208 | config.add_view(robots_redirect, route_name='robots') |
|
197 | 209 | config.add_route('robots', '/robots.txt') |
|
198 | 210 | |
|
199 | 211 | config.add_static_view( |
|
200 | 212 | '_static/deform', 'deform:static') |
|
201 | 213 | config.add_static_view( |
|
202 | 214 | '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24) |
|
203 | 215 | |
|
204 | 216 | |
|
205 | 217 | def includeme(config): |
|
206 | 218 | settings = config.registry.settings |
|
207 | 219 | config.set_request_factory(Request) |
|
208 | 220 | |
|
209 | 221 | # plugin information |
|
210 | 222 | config.registry.rhodecode_plugins = collections.OrderedDict() |
|
211 | 223 | |
|
212 | 224 | config.add_directive( |
|
213 | 225 | 'register_rhodecode_plugin', register_rhodecode_plugin) |
|
214 | 226 | |
|
215 | 227 | config.add_directive('configure_celery', configure_celery) |
|
216 | 228 | |
|
217 | 229 | if asbool(settings.get('appenlight', 'false')): |
|
218 | 230 | config.include('appenlight_client.ext.pyramid_tween') |
|
219 | 231 | |
|
220 | 232 | # Includes which are required. The application would fail without them. |
|
221 | 233 | config.include('pyramid_mako') |
|
222 | 234 | config.include('pyramid_beaker') |
|
223 | 235 | config.include('rhodecode.lib.caches') |
|
224 | 236 | config.include('rhodecode.lib.rc_cache') |
|
225 | 237 | |
|
226 | 238 | config.include('rhodecode.authentication') |
|
227 | 239 | config.include('rhodecode.integrations') |
|
228 | 240 | |
|
229 | 241 | # apps |
|
230 | 242 | config.include('rhodecode.apps._base') |
|
231 | 243 | config.include('rhodecode.apps.ops') |
|
232 | 244 | |
|
233 | 245 | config.include('rhodecode.apps.admin') |
|
234 | 246 | config.include('rhodecode.apps.channelstream') |
|
235 | 247 | config.include('rhodecode.apps.login') |
|
236 | 248 | config.include('rhodecode.apps.home') |
|
237 | 249 | config.include('rhodecode.apps.journal') |
|
238 | 250 | config.include('rhodecode.apps.repository') |
|
239 | 251 | config.include('rhodecode.apps.repo_group') |
|
240 | 252 | config.include('rhodecode.apps.user_group') |
|
241 | 253 | config.include('rhodecode.apps.search') |
|
242 | 254 | config.include('rhodecode.apps.user_profile') |
|
243 | 255 | config.include('rhodecode.apps.user_group_profile') |
|
244 | 256 | config.include('rhodecode.apps.my_account') |
|
245 | 257 | config.include('rhodecode.apps.svn_support') |
|
246 | 258 | config.include('rhodecode.apps.ssh_support') |
|
247 | 259 | config.include('rhodecode.apps.gist') |
|
248 | 260 | |
|
249 | 261 | config.include('rhodecode.apps.debug_style') |
|
250 | 262 | config.include('rhodecode.tweens') |
|
251 | 263 | config.include('rhodecode.api') |
|
252 | 264 | |
|
253 | 265 | config.add_route( |
|
254 | 266 | 'rhodecode_support', 'https://rhodecode.com/help/', static=True) |
|
255 | 267 | |
|
256 | 268 | config.add_translation_dirs('rhodecode:i18n/') |
|
257 | 269 | settings['default_locale_name'] = settings.get('lang', 'en') |
|
258 | 270 | |
|
259 | 271 | # Add subscribers. |
|
260 | 272 | config.add_subscriber(inject_app_settings, ApplicationCreated) |
|
261 | 273 | config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated) |
|
262 | 274 | config.add_subscriber(write_metadata_if_needed, ApplicationCreated) |
|
263 | 275 | config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated) |
|
264 | 276 | |
|
265 | 277 | # events |
|
266 | 278 | # TODO(marcink): this should be done when pyramid migration is finished |
|
267 | 279 | # config.add_subscriber( |
|
268 | 280 | # 'rhodecode.integrations.integrations_event_handler', |
|
269 | 281 | # 'rhodecode.events.RhodecodeEvent') |
|
270 | 282 | |
|
271 | 283 | # request custom methods |
|
272 | 284 | config.add_request_method( |
|
273 | 285 | 'rhodecode.lib.partial_renderer.get_partial_renderer', |
|
274 | 286 | 'get_partial_renderer') |
|
275 | 287 | |
|
276 | 288 | # Set the authorization policy. |
|
277 | 289 | authz_policy = ACLAuthorizationPolicy() |
|
278 | 290 | config.set_authorization_policy(authz_policy) |
|
279 | 291 | |
|
280 | 292 | # Set the default renderer for HTML templates to mako. |
|
281 | 293 | config.add_mako_renderer('.html') |
|
282 | 294 | |
|
283 | 295 | config.add_renderer( |
|
284 | 296 | name='json_ext', |
|
285 | 297 | factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json') |
|
286 | 298 | |
|
287 | 299 | # include RhodeCode plugins |
|
288 | 300 | includes = aslist(settings.get('rhodecode.includes', [])) |
|
289 | 301 | for inc in includes: |
|
290 | 302 | config.include(inc) |
|
291 | 303 | |
|
292 | 304 | # custom not found view, if our pyramid app doesn't know how to handle |
|
293 | 305 | # the request pass it to potential VCS handling ap |
|
294 | 306 | config.add_notfound_view(not_found_view) |
|
295 | 307 | if not settings.get('debugtoolbar.enabled', False): |
|
296 | 308 | # disabled debugtoolbar handle all exceptions via the error_handlers |
|
297 | 309 | config.add_view(error_handler, context=Exception) |
|
298 | 310 | |
|
299 | 311 | # all errors including 403/404/50X |
|
300 | 312 | config.add_view(error_handler, context=HTTPError) |
|
301 | 313 | |
|
302 | 314 | |
|
303 | 315 | def wrap_app_in_wsgi_middlewares(pyramid_app, config): |
|
304 | 316 | """ |
|
305 | 317 | Apply outer WSGI middlewares around the application. |
|
306 | 318 | """ |
|
307 | 319 | settings = config.registry.settings |
|
308 | 320 | |
|
309 | 321 | # enable https redirects based on HTTP_X_URL_SCHEME set by proxy |
|
310 | 322 | pyramid_app = HttpsFixup(pyramid_app, settings) |
|
311 | 323 | |
|
312 | 324 | pyramid_app, _ae_client = wrap_in_appenlight_if_enabled( |
|
313 | 325 | pyramid_app, settings) |
|
314 | 326 | config.registry.ae_client = _ae_client |
|
315 | 327 | |
|
316 | 328 | if settings['gzip_responses']: |
|
317 | 329 | pyramid_app = make_gzip_middleware( |
|
318 | 330 | pyramid_app, settings, compress_level=1) |
|
319 | 331 | |
|
320 | 332 | # this should be the outer most middleware in the wsgi stack since |
|
321 | 333 | # middleware like Routes make database calls |
|
322 | 334 | def pyramid_app_with_cleanup(environ, start_response): |
|
323 | 335 | try: |
|
324 | 336 | return pyramid_app(environ, start_response) |
|
325 | 337 | finally: |
|
326 | 338 | # Dispose current database session and rollback uncommitted |
|
327 | 339 | # transactions. |
|
328 | 340 | meta.Session.remove() |
|
329 | 341 | |
|
330 | 342 | # In a single threaded mode server, on non sqlite db we should have |
|
331 | 343 | # '0 Current Checked out connections' at the end of a request, |
|
332 | 344 | # if not, then something, somewhere is leaving a connection open |
|
333 | 345 | pool = meta.Base.metadata.bind.engine.pool |
|
334 | 346 | log.debug('sa pool status: %s', pool.status()) |
|
335 | 347 | |
|
336 | 348 | return pyramid_app_with_cleanup |
|
337 | 349 | |
|
338 | 350 | |
|
339 | 351 | def sanitize_settings_and_apply_defaults(settings): |
|
340 | 352 | """ |
|
341 | 353 | Applies settings defaults and does all type conversion. |
|
342 | 354 | |
|
343 | 355 | We would move all settings parsing and preparation into this place, so that |
|
344 | 356 | we have only one place left which deals with this part. The remaining parts |
|
345 | 357 | of the application would start to rely fully on well prepared settings. |
|
346 | 358 | |
|
347 | 359 | This piece would later be split up per topic to avoid a big fat monster |
|
348 | 360 | function. |
|
349 | 361 | """ |
|
350 | 362 | |
|
351 | 363 | settings.setdefault('rhodecode.edition', 'Community Edition') |
|
352 | 364 | |
|
353 | 365 | if 'mako.default_filters' not in settings: |
|
354 | 366 | # set custom default filters if we don't have it defined |
|
355 | 367 | settings['mako.imports'] = 'from rhodecode.lib.base import h_filter' |
|
356 | 368 | settings['mako.default_filters'] = 'h_filter' |
|
357 | 369 | |
|
358 | 370 | if 'mako.directories' not in settings: |
|
359 | 371 | mako_directories = settings.setdefault('mako.directories', [ |
|
360 | 372 | # Base templates of the original application |
|
361 | 373 | 'rhodecode:templates', |
|
362 | 374 | ]) |
|
363 | 375 | log.debug( |
|
364 | 376 | "Using the following Mako template directories: %s", |
|
365 | 377 | mako_directories) |
|
366 | 378 | |
|
367 | 379 | # Default includes, possible to change as a user |
|
368 | 380 | pyramid_includes = settings.setdefault('pyramid.includes', [ |
|
369 | 381 | 'rhodecode.lib.middleware.request_wrapper', |
|
370 | 382 | ]) |
|
371 | 383 | log.debug( |
|
372 | 384 | "Using the following pyramid.includes: %s", |
|
373 | 385 | pyramid_includes) |
|
374 | 386 | |
|
375 | 387 | # TODO: johbo: Re-think this, usually the call to config.include |
|
376 | 388 | # should allow to pass in a prefix. |
|
377 | 389 | settings.setdefault('rhodecode.api.url', '/_admin/api') |
|
378 | 390 | |
|
379 | 391 | # Sanitize generic settings. |
|
380 | 392 | _list_setting(settings, 'default_encoding', 'UTF-8') |
|
381 | 393 | _bool_setting(settings, 'is_test', 'false') |
|
382 | 394 | _bool_setting(settings, 'gzip_responses', 'false') |
|
383 | 395 | |
|
384 | 396 | # Call split out functions that sanitize settings for each topic. |
|
385 | 397 | _sanitize_appenlight_settings(settings) |
|
386 | 398 | _sanitize_vcs_settings(settings) |
|
387 | 399 | _sanitize_cache_settings(settings) |
|
388 | 400 | |
|
389 | 401 | # configure instance id |
|
390 | 402 | config_utils.set_instance_id(settings) |
|
391 | 403 | |
|
392 | 404 | return settings |
|
393 | 405 | |
|
394 | 406 | |
|
395 | 407 | def _sanitize_appenlight_settings(settings): |
|
396 | 408 | _bool_setting(settings, 'appenlight', 'false') |
|
397 | 409 | |
|
398 | 410 | |
|
399 | 411 | def _sanitize_vcs_settings(settings): |
|
400 | 412 | """ |
|
401 | 413 | Applies settings defaults and does type conversion for all VCS related |
|
402 | 414 | settings. |
|
403 | 415 | """ |
|
404 | 416 | _string_setting(settings, 'vcs.svn.compatible_version', '') |
|
405 | 417 | _string_setting(settings, 'git_rev_filter', '--all') |
|
406 | 418 | _string_setting(settings, 'vcs.hooks.protocol', 'http') |
|
407 | 419 | _string_setting(settings, 'vcs.hooks.host', '127.0.0.1') |
|
408 | 420 | _string_setting(settings, 'vcs.scm_app_implementation', 'http') |
|
409 | 421 | _string_setting(settings, 'vcs.server', '') |
|
410 | 422 | _string_setting(settings, 'vcs.server.log_level', 'debug') |
|
411 | 423 | _string_setting(settings, 'vcs.server.protocol', 'http') |
|
412 | 424 | _bool_setting(settings, 'startup.import_repos', 'false') |
|
413 | 425 | _bool_setting(settings, 'vcs.hooks.direct_calls', 'false') |
|
414 | 426 | _bool_setting(settings, 'vcs.server.enable', 'true') |
|
415 | 427 | _bool_setting(settings, 'vcs.start_server', 'false') |
|
416 | 428 | _list_setting(settings, 'vcs.backends', 'hg, git, svn') |
|
417 | 429 | _int_setting(settings, 'vcs.connection_timeout', 3600) |
|
418 | 430 | |
|
419 | 431 | # Support legacy values of vcs.scm_app_implementation. Legacy |
|
420 | 432 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http' |
|
421 | 433 | # which is now mapped to 'http'. |
|
422 | 434 | scm_app_impl = settings['vcs.scm_app_implementation'] |
|
423 | 435 | if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http': |
|
424 | 436 | settings['vcs.scm_app_implementation'] = 'http' |
|
425 | 437 | |
|
426 | 438 | |
|
427 | 439 | def _sanitize_cache_settings(settings): |
|
428 | 440 | _string_setting(settings, 'cache_dir', |
|
429 | 441 | os.path.join(tempfile.gettempdir(), 'rc_cache')) |
|
430 | 442 | # cache_perms |
|
431 | 443 | _string_setting( |
|
432 | 444 | settings, |
|
433 | 445 | 'rc_cache.cache_perms.backend', |
|
434 | 446 | 'dogpile.cache.rc.file_namespace') |
|
435 | 447 | _int_setting( |
|
436 | 448 | settings, |
|
437 | 449 | 'rc_cache.cache_perms.expiration_time', |
|
438 | 450 | 60) |
|
439 | 451 | _string_setting( |
|
440 | 452 | settings, |
|
441 | 453 | 'rc_cache.cache_perms.arguments.filename', |
|
442 | 454 | os.path.join(tempfile.gettempdir(), 'rc_cache_1')) |
|
443 | 455 | |
|
444 | 456 | # cache_repo |
|
445 | 457 | _string_setting( |
|
446 | 458 | settings, |
|
447 | 459 | 'rc_cache.cache_repo.backend', |
|
448 | 460 | 'dogpile.cache.rc.file_namespace') |
|
449 | 461 | _int_setting( |
|
450 | 462 | settings, |
|
451 | 463 | 'rc_cache.cache_repo.expiration_time', |
|
452 | 464 | 60) |
|
453 | 465 | _string_setting( |
|
454 | 466 | settings, |
|
455 | 467 | 'rc_cache.cache_repo.arguments.filename', |
|
456 | 468 | os.path.join(tempfile.gettempdir(), 'rc_cache_2')) |
|
457 | 469 | |
|
458 | 470 | # sql_cache_short |
|
459 | 471 | _string_setting( |
|
460 | 472 | settings, |
|
461 | 473 | 'rc_cache.sql_cache_short.backend', |
|
462 | 474 | 'dogpile.cache.rc.memory_lru') |
|
463 | 475 | _int_setting( |
|
464 | 476 | settings, |
|
465 | 477 | 'rc_cache.sql_cache_short.expiration_time', |
|
466 | 478 | 30) |
|
467 | 479 | _int_setting( |
|
468 | 480 | settings, |
|
469 | 481 | 'rc_cache.sql_cache_short.max_size', |
|
470 | 482 | 10000) |
|
471 | 483 | |
|
472 | 484 | |
|
473 | 485 | def _int_setting(settings, name, default): |
|
474 | 486 | settings[name] = int(settings.get(name, default)) |
|
475 | 487 | |
|
476 | 488 | |
|
477 | 489 | def _bool_setting(settings, name, default): |
|
478 | 490 | input_val = settings.get(name, default) |
|
479 | 491 | if isinstance(input_val, unicode): |
|
480 | 492 | input_val = input_val.encode('utf8') |
|
481 | 493 | settings[name] = asbool(input_val) |
|
482 | 494 | |
|
483 | 495 | |
|
484 | 496 | def _list_setting(settings, name, default): |
|
485 | 497 | raw_value = settings.get(name, default) |
|
486 | 498 | |
|
487 | 499 | old_separator = ',' |
|
488 | 500 | if old_separator in raw_value: |
|
489 | 501 | # If we get a comma separated list, pass it to our own function. |
|
490 | 502 | settings[name] = rhodecode_aslist(raw_value, sep=old_separator) |
|
491 | 503 | else: |
|
492 | 504 | # Otherwise we assume it uses pyramids space/newline separation. |
|
493 | 505 | settings[name] = aslist(raw_value) |
|
494 | 506 | |
|
495 | 507 | |
|
496 | 508 | def _string_setting(settings, name, default, lower=True): |
|
497 | 509 | value = settings.get(name, default) |
|
498 | 510 | if lower: |
|
499 | 511 | value = value.lower() |
|
500 | 512 | settings[name] = value |
|
501 | 513 | |
|
502 | 514 | |
|
503 | 515 | def _substitute_values(mapping, substitutions): |
|
504 | 516 | result = { |
|
505 | 517 | # Note: Cannot use regular replacements, since they would clash |
|
506 | 518 | # with the implementation of ConfigParser. Using "format" instead. |
|
507 | 519 | key: value.format(**substitutions) |
|
508 | 520 | for key, value in mapping.items() |
|
509 | 521 | } |
|
510 | 522 | return result |
@@ -1,385 +1,392 b'' | |||
|
1 | 1 | |
|
2 | 2 | // Contains the style definitions used for .main-content |
|
3 | 3 | // elements which are mainly around the admin settings. |
|
4 | 4 | |
|
5 | 5 | |
|
6 | 6 | // TODO: johbo: Integrate in a better way, this is for "main content" which |
|
7 | 7 | // should not have a limit on the width. |
|
8 | 8 | .main-content-full { |
|
9 | 9 | clear: both; |
|
10 | 10 | } |
|
11 | 11 | |
|
12 | 12 | |
|
13 | 13 | .main-content { |
|
14 | 14 | max-width: @maincontent-maxwidth; |
|
15 | 15 | |
|
16 | 16 | h3, |
|
17 | 17 | // TODO: johbo: Change templates to use h3 instead of h4 here |
|
18 | 18 | h4 { |
|
19 | 19 | line-height: 1em; |
|
20 | 20 | } |
|
21 | 21 | |
|
22 | 22 | // TODO: johbo: Check if we can do that on a global level |
|
23 | 23 | table { |
|
24 | 24 | th { |
|
25 | 25 | padding: 0; |
|
26 | 26 | } |
|
27 | 27 | td.field{ |
|
28 | 28 | .help-block{ |
|
29 | 29 | margin-left: 0; |
|
30 | 30 | } |
|
31 | 31 | } |
|
32 | 32 | } |
|
33 | 33 | |
|
34 | 34 | // TODO: johbo: Tweak this into the general styling, for a full width |
|
35 | 35 | // textarea |
|
36 | 36 | .textarea-full { |
|
37 | 37 | // 2x 10px padding and 2x 1px border |
|
38 | 38 | margin-right: 22px; |
|
39 | 39 | } |
|
40 | 40 | |
|
41 | 41 | } |
|
42 | 42 | |
|
43 | 43 | |
|
44 | 44 | // TODO: johbo: duplicated, think about a mixins.less |
|
45 | 45 | .block-left{ |
|
46 | 46 | float: left; |
|
47 | 47 | } |
|
48 | 48 | |
|
49 | 49 | .form { |
|
50 | 50 | .checkboxes { |
|
51 | 51 | // TODO: johbo: Should be changed in .checkboxes already |
|
52 | 52 | width: auto; |
|
53 | 53 | } |
|
54 | 54 | |
|
55 | 55 | // TODO: johbo: some settings pages are broken and don't have the .buttons |
|
56 | 56 | // inside the .fields, tweak those templates and remove this. |
|
57 | 57 | .buttons { |
|
58 | 58 | margin-top: @textmargin; |
|
59 | 59 | } |
|
60 | 60 | |
|
61 | 61 | .help-block { |
|
62 | 62 | display: block; |
|
63 | 63 | margin-left: @label-width; |
|
64 | 64 | &.pre-formatting { |
|
65 | 65 | white-space: pre; |
|
66 | 66 | } |
|
67 | 67 | } |
|
68 | 68 | |
|
69 | 69 | .action_button { |
|
70 | 70 | color: @grey4; |
|
71 | 71 | } |
|
72 | 72 | } |
|
73 | 73 | |
|
74 | 74 | .main-content-full-width { |
|
75 | 75 | .main-content; |
|
76 | 76 | width: 100%; |
|
77 | 77 | min-width: 100%; |
|
78 | 78 | } |
|
79 | 79 | |
|
80 | .main-content-auto-width { | |
|
81 | .main-content; | |
|
82 | width: auto; | |
|
83 | min-width: 100%; | |
|
84 | max-width: inherit; | |
|
85 | } | |
|
86 | ||
|
80 | 87 | .field { |
|
81 | 88 | clear: left; |
|
82 | 89 | margin-bottom: @padding; |
|
83 | 90 | |
|
84 | 91 | } |
|
85 | 92 | |
|
86 | 93 | .input-monospace { |
|
87 | 94 | font-family: @font-family-monospace; |
|
88 | 95 | } |
|
89 | 96 | |
|
90 | 97 | .fields { |
|
91 | 98 | label { |
|
92 | 99 | color: @grey2; |
|
93 | 100 | } |
|
94 | 101 | |
|
95 | 102 | .field { |
|
96 | 103 | clear: right; |
|
97 | 104 | margin-bottom: @textmargin; |
|
98 | 105 | width: 100%; |
|
99 | 106 | |
|
100 | 107 | .label { |
|
101 | 108 | float: left; |
|
102 | 109 | margin-right: @form-vertical-margin; |
|
103 | 110 | margin-top: 0; |
|
104 | 111 | padding-top: @input-padding-px + @border-thickness-inputs; |
|
105 | 112 | width: @label-width - @form-vertical-margin; |
|
106 | 113 | } |
|
107 | 114 | // used in forms for fields that show just text |
|
108 | 115 | .label-text { |
|
109 | 116 | .label; |
|
110 | 117 | padding-top: 5px; |
|
111 | 118 | } |
|
112 | 119 | // Used to position content on the right side of a .label |
|
113 | 120 | .content, |
|
114 | 121 | .side-by-side-selector { |
|
115 | 122 | padding-top: @input-padding-px + @input-border-thickness; |
|
116 | 123 | } |
|
117 | 124 | |
|
118 | 125 | .checkboxes, |
|
119 | 126 | .input, |
|
120 | 127 | .select, |
|
121 | 128 | .textarea, |
|
122 | 129 | .content { |
|
123 | 130 | float: none; |
|
124 | 131 | margin-left: @label-width; |
|
125 | 132 | |
|
126 | 133 | .help-block { |
|
127 | 134 | margin-left: 0; |
|
128 | 135 | } |
|
129 | 136 | } |
|
130 | 137 | |
|
131 | 138 | .checkboxes, |
|
132 | 139 | .input, |
|
133 | 140 | .select { |
|
134 | 141 | .help-block { |
|
135 | 142 | display: block; |
|
136 | 143 | } |
|
137 | 144 | } |
|
138 | 145 | |
|
139 | 146 | .checkboxes, |
|
140 | 147 | .radios { |
|
141 | 148 | // TODO: johbo: We get a 4px margin from the from-bootstrap, |
|
142 | 149 | // compensating here to align well with labels on the left. |
|
143 | 150 | padding-top: @input-padding-px + @input-border-thickness - 3px; |
|
144 | 151 | } |
|
145 | 152 | |
|
146 | 153 | .checkbox, |
|
147 | 154 | .radio { |
|
148 | 155 | display: block; |
|
149 | 156 | width: auto; |
|
150 | 157 | } |
|
151 | 158 | |
|
152 | 159 | .checkbox + .checkbox { |
|
153 | 160 | display: block; |
|
154 | 161 | } |
|
155 | 162 | |
|
156 | 163 | .input, |
|
157 | 164 | .select { |
|
158 | 165 | .help-block, |
|
159 | 166 | .info-block { |
|
160 | 167 | margin-top: @form-vertical-margin / 2; |
|
161 | 168 | } |
|
162 | 169 | } |
|
163 | 170 | |
|
164 | 171 | .input { |
|
165 | 172 | .medium { |
|
166 | 173 | width: @fields-input-m; |
|
167 | 174 | } |
|
168 | 175 | .large { |
|
169 | 176 | width: @fields-input-l; |
|
170 | 177 | } |
|
171 | 178 | |
|
172 | 179 | .text-as-placeholder { |
|
173 | 180 | padding-top: @input-padding-px + @border-thickness-inputs; |
|
174 | 181 | } |
|
175 | 182 | } |
|
176 | 183 | |
|
177 | 184 | // TODO: johbo: Try to find a better integration of this bit. |
|
178 | 185 | // When using a select2 inside of a field, it should not have the |
|
179 | 186 | // top margin. |
|
180 | 187 | .select .drop-menu { |
|
181 | 188 | margin-top: 0; |
|
182 | 189 | } |
|
183 | 190 | |
|
184 | 191 | .textarea { |
|
185 | 192 | float: none; |
|
186 | 193 | |
|
187 | 194 | textarea { |
|
188 | 195 | // TODO: johbo: From somewhere we get a clear which does |
|
189 | 196 | // more harm than good here. |
|
190 | 197 | clear: none; |
|
191 | 198 | } |
|
192 | 199 | |
|
193 | 200 | .CodeMirror { |
|
194 | 201 | // TODO: johbo: Tweak to position the .help-block nicer, |
|
195 | 202 | // figure out how to apply for .text-block instead. |
|
196 | 203 | margin-bottom: 10px; |
|
197 | 204 | } |
|
198 | 205 | |
|
199 | 206 | // TODO: johbo: Check if we can remove the grey background on |
|
200 | 207 | // the global level and remove this if possible. |
|
201 | 208 | .help-block { |
|
202 | 209 | background: transparent; |
|
203 | 210 | padding: 0; |
|
204 | 211 | } |
|
205 | 212 | } |
|
206 | 213 | |
|
207 | 214 | &.tag_patterns, |
|
208 | 215 | &.branch_patterns { |
|
209 | 216 | |
|
210 | 217 | input { |
|
211 | 218 | max-width: 430px; |
|
212 | 219 | } |
|
213 | 220 | } |
|
214 | 221 | } |
|
215 | 222 | |
|
216 | 223 | .field-sm { |
|
217 | 224 | .label { |
|
218 | 225 | padding-top: @input-padding-px / 2 + @input-border-thickness; |
|
219 | 226 | } |
|
220 | 227 | .checkboxes, |
|
221 | 228 | .radios { |
|
222 | 229 | // TODO: johbo: We get a 4px margin from the from-bootstrap, |
|
223 | 230 | // compensating here to align well with labels on the left. |
|
224 | 231 | padding-top: @input-padding-px / 2 + @input-border-thickness - 3px; |
|
225 | 232 | } |
|
226 | 233 | } |
|
227 | 234 | |
|
228 | 235 | .field.customhooks { |
|
229 | 236 | .label { |
|
230 | 237 | padding-top: 0; |
|
231 | 238 | } |
|
232 | 239 | .input-wrapper { |
|
233 | 240 | padding-right: 25px; |
|
234 | 241 | |
|
235 | 242 | input { |
|
236 | 243 | width: 100%; |
|
237 | 244 | } |
|
238 | 245 | } |
|
239 | 246 | .input { |
|
240 | 247 | padding-right: 25px; |
|
241 | 248 | } |
|
242 | 249 | } |
|
243 | 250 | |
|
244 | 251 | .buttons { |
|
245 | 252 | // TODO: johbo: define variable for this value. |
|
246 | 253 | // Note that this should be 40px but since most elements add some |
|
247 | 254 | // space in the bottom, we are with 20 closer to 40. |
|
248 | 255 | margin-top: 20px; |
|
249 | 256 | clear: both; |
|
250 | 257 | margin-bottom: @padding; |
|
251 | 258 | } |
|
252 | 259 | |
|
253 | 260 | .desc{ |
|
254 | 261 | margin-right: @textmargin; |
|
255 | 262 | } |
|
256 | 263 | |
|
257 | 264 | input, |
|
258 | 265 | .drop-menu { |
|
259 | 266 | margin-right: @padding/3; |
|
260 | 267 | } |
|
261 | 268 | |
|
262 | 269 | } |
|
263 | 270 | |
|
264 | 271 | .form-vertical .fields .field { |
|
265 | 272 | |
|
266 | 273 | .label { |
|
267 | 274 | float: none; |
|
268 | 275 | width: auto; |
|
269 | 276 | } |
|
270 | 277 | |
|
271 | 278 | .checkboxes, |
|
272 | 279 | .input, |
|
273 | 280 | .select, |
|
274 | 281 | .textarea { |
|
275 | 282 | margin-left: 0; |
|
276 | 283 | } |
|
277 | 284 | |
|
278 | 285 | // TODO: johbo: had to tweak the width here to make it big enough for |
|
279 | 286 | // the license. |
|
280 | 287 | .textarea.editor { |
|
281 | 288 | max-width: none; |
|
282 | 289 | } |
|
283 | 290 | |
|
284 | 291 | .textarea.large textarea { |
|
285 | 292 | min-height: 200px; |
|
286 | 293 | } |
|
287 | 294 | |
|
288 | 295 | .help-block { |
|
289 | 296 | margin-left: 0; |
|
290 | 297 | } |
|
291 | 298 | } |
|
292 | 299 | |
|
293 | 300 | |
|
294 | 301 | |
|
295 | 302 | |
|
296 | 303 | .main-content { |
|
297 | 304 | .block-left; |
|
298 | 305 | |
|
299 | 306 | .section { |
|
300 | 307 | margin-bottom: @space; |
|
301 | 308 | } |
|
302 | 309 | |
|
303 | 310 | |
|
304 | 311 | // Table aligning same way as forms in admin section, e.g. |
|
305 | 312 | // python packages table |
|
306 | 313 | table.formalign { |
|
307 | 314 | float: left; |
|
308 | 315 | width: auto; |
|
309 | 316 | |
|
310 | 317 | .label { |
|
311 | 318 | width: @label-width; |
|
312 | 319 | } |
|
313 | 320 | |
|
314 | 321 | } |
|
315 | 322 | |
|
316 | 323 | |
|
317 | 324 | table.issuetracker { |
|
318 | 325 | |
|
319 | 326 | color: @text-color; |
|
320 | 327 | |
|
321 | 328 | .issue-tracker-example { |
|
322 | 329 | color: @grey4; |
|
323 | 330 | } |
|
324 | 331 | } |
|
325 | 332 | |
|
326 | 333 | .side-by-side-selector{ |
|
327 | 334 | .left-group, |
|
328 | 335 | .middle-group, |
|
329 | 336 | .right-group{ |
|
330 | 337 | float: left; |
|
331 | 338 | } |
|
332 | 339 | |
|
333 | 340 | .left-group, |
|
334 | 341 | .right-group{ |
|
335 | 342 | width: 45%; |
|
336 | 343 | text-align: center; |
|
337 | 344 | |
|
338 | 345 | label{ |
|
339 | 346 | width: 100%; |
|
340 | 347 | text-align: left; |
|
341 | 348 | } |
|
342 | 349 | |
|
343 | 350 | select{ |
|
344 | 351 | width: 100%; |
|
345 | 352 | background: none; |
|
346 | 353 | border-color: @border-highlight-color; |
|
347 | 354 | color: @text-color; |
|
348 | 355 | font-family: @text-light; |
|
349 | 356 | font-size: @basefontsize; |
|
350 | 357 | color: @grey1; |
|
351 | 358 | padding: @textmargin/2; |
|
352 | 359 | } |
|
353 | 360 | |
|
354 | 361 | select:after{ |
|
355 | 362 | content: ""; |
|
356 | 363 | } |
|
357 | 364 | |
|
358 | 365 | } |
|
359 | 366 | |
|
360 | 367 | .middle-group{ |
|
361 | 368 | width: 10%; |
|
362 | 369 | text-align: center; |
|
363 | 370 | padding-top: 4em; |
|
364 | 371 | i { |
|
365 | 372 | font-size: 18px; |
|
366 | 373 | cursor: pointer; |
|
367 | 374 | line-height: 2em; |
|
368 | 375 | } |
|
369 | 376 | } |
|
370 | 377 | |
|
371 | 378 | } |
|
372 | 379 | |
|
373 | 380 | .permissions_boxes{ |
|
374 | 381 | label, .label{ |
|
375 | 382 | margin-right: @textmargin/2; |
|
376 | 383 | } |
|
377 | 384 | } |
|
378 | 385 | |
|
379 | 386 | .radios{ |
|
380 | 387 | label{ |
|
381 | 388 | margin-right: @textmargin; |
|
382 | 389 | } |
|
383 | 390 | } |
|
384 | 391 | } |
|
385 | 392 |
@@ -1,53 +1,53 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <%inherit file="/base/base.mako"/> |
|
3 | 3 | |
|
4 | 4 | <%def name="title()"> |
|
5 | 5 | ${_('Settings administration')} |
|
6 | 6 | %if c.rhodecode_name: |
|
7 | 7 | · ${h.branding(c.rhodecode_name)} |
|
8 | 8 | %endif |
|
9 | 9 | </%def> |
|
10 | 10 | |
|
11 | 11 | <%def name="breadcrumbs_links()"> |
|
12 | 12 | ${h.link_to(_('Admin'),h.route_path('admin_home'))} |
|
13 | 13 | » |
|
14 | 14 | ${_('Settings')} |
|
15 | 15 | </%def> |
|
16 | 16 | |
|
17 | 17 | <%def name="menu_bar_nav()"> |
|
18 | 18 | ${self.menu_items(active='admin')} |
|
19 | 19 | </%def> |
|
20 | 20 | |
|
21 | 21 | <%def name="side_bar_nav()"> |
|
22 | 22 | % for navitem in c.navlist: |
|
23 | 23 | <li class="${'active' if c.active in navitem.active_list else ''}"> |
|
24 | 24 | <a href="${navitem.url}">${navitem.name}</a> |
|
25 | 25 | </li> |
|
26 | 26 | % endfor |
|
27 | 27 | </%def> |
|
28 | 28 | |
|
29 | 29 | <%def name="main_content()"> |
|
30 | 30 | <%include file="/admin/settings/settings_${c.active}.mako"/> |
|
31 | 31 | </%def> |
|
32 | 32 | |
|
33 | 33 | <%def name="main()"> |
|
34 | 34 | <div class="box"> |
|
35 | 35 | <div class="title"> |
|
36 | 36 | ${self.breadcrumbs()} |
|
37 | 37 | </div> |
|
38 | 38 | |
|
39 | 39 | ##main |
|
40 | 40 | <div class='sidebar-col-wrapper'> |
|
41 | 41 | <div class="sidebar"> |
|
42 | 42 | <ul class="nav nav-pills nav-stacked"> |
|
43 | 43 | ${self.side_bar_nav()} |
|
44 | 44 | </ul> |
|
45 | 45 | </div> |
|
46 | 46 | |
|
47 |
<div class="main-content- |
|
|
47 | <div class="main-content-auto-width"> | |
|
48 | 48 | ${self.main_content()} |
|
49 | 49 | </div> |
|
50 | 50 | </div> |
|
51 | 51 | </div> |
|
52 | 52 | |
|
53 | 53 | </%def> No newline at end of file |
@@ -1,76 +1,84 b'' | |||
|
1 | 1 | ## -*- coding: utf-8 -*- |
|
2 | 2 | <!DOCTYPE html> |
|
3 | 3 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
4 | 4 | <head> |
|
5 | 5 | <title>Error - ${c.error_message}</title> |
|
6 | 6 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
7 | 7 | <meta name="robots" content="index, nofollow"/> |
|
8 | 8 | <link rel="icon" href="${h.asset('images/favicon.ico')}" sizes="16x16 32x32" type="image/png" /> |
|
9 | 9 | |
|
10 | 10 | <meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> |
|
11 | 11 | %if c.redirect_time: |
|
12 | 12 | <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/> |
|
13 | 13 | %endif |
|
14 | 14 | |
|
15 | 15 | <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/> |
|
16 | 16 | <!--[if IE]> |
|
17 | 17 | <link rel="stylesheet" type="text/css" href="${h.asset('css/ie.css')}" media="screen"/> |
|
18 | 18 | <![endif]--> |
|
19 | 19 | <style>body { background:#eeeeee; }</style> |
|
20 | 20 | <script type="text/javascript"> |
|
21 | 21 | // register templateContext to pass template variables to JS |
|
22 | 22 | var templateContext = {timeago: {}}; |
|
23 | 23 | </script> |
|
24 | 24 | <script type="text/javascript" src="${h.asset('js/scripts.js', ver=c.rhodecode_version_hash)}"></script> |
|
25 | 25 | </head> |
|
26 | 26 | <body> |
|
27 | 27 | |
|
28 | 28 | <div class="wrapper error_page"> |
|
29 | 29 | <div class="sidebar"> |
|
30 | 30 | <a href="${h.route_path('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a> |
|
31 | 31 | </div> |
|
32 | 32 | <div class="main-content"> |
|
33 | 33 | <h1> |
|
34 | 34 | <span class="error-branding"> |
|
35 | 35 | ${h.branding(c.rhodecode_name)} |
|
36 | 36 | </span><br/> |
|
37 | 37 | ${c.error_message} | <span class="error_message">${c.error_explanation}</span> |
|
38 | 38 | </h1> |
|
39 | 39 | % if c.messages: |
|
40 | 40 | % for message in c.messages: |
|
41 | 41 | <div class="alert alert-${message.category}">${message}</div> |
|
42 | 42 | % endfor |
|
43 | 43 | % endif |
|
44 | 44 | %if c.redirect_time: |
|
45 | 45 | <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p> |
|
46 | 46 | %endif |
|
47 | 47 | <div class="inner-column"> |
|
48 | 48 | <h4>Possible Causes</h4> |
|
49 | 49 | <ul> |
|
50 | 50 | % if c.causes: |
|
51 | 51 | %for cause in c.causes: |
|
52 | 52 | <li>${cause}</li> |
|
53 | 53 | %endfor |
|
54 | 54 | %else: |
|
55 | 55 | <li>The resource may have been deleted.</li> |
|
56 | 56 | <li>You may not have access to this repository.</li> |
|
57 | 57 | <li>The link may be incorrect.</li> |
|
58 | 58 | %endif |
|
59 | 59 | </ul> |
|
60 | 60 | </div> |
|
61 | 61 | <div class="inner-column"> |
|
62 | 62 | <h4>Support</h4> |
|
63 | <p>For support, go to <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>. | |
|
63 | <p>For help and support, go to the <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support Page')}</a>. | |
|
64 | 64 | It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>. |
|
65 | 65 | </p> |
|
66 | % if c.show_exception_id: | |
|
67 | <p> | |
|
68 | Super Admin can see detailed traceback information from this exception by checking the below Exception ID. | |
|
69 | Please include the below link for further details of this exception. | |
|
70 | ||
|
71 | Exception ID: <a href="${c.exception_id_url}">${c.exception_id}</a> | |
|
72 | </p> | |
|
73 | % endif | |
|
66 | 74 | </div> |
|
67 | 75 | <div class="inner-column"> |
|
68 | 76 | <h4>Documentation</h4> |
|
69 | 77 | <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p> |
|
70 | 78 | </div> |
|
71 | 79 | </div> |
|
72 | 80 | </div> |
|
73 | 81 | |
|
74 | 82 | </body> |
|
75 | 83 | |
|
76 | 84 | </html> |
General Comments 0
You need to be logged in to leave comments.
Login now