##// END OF EJS Templates
exceptions: added new exception tracking capability....
marcink -
r2907:5eed39e5 default
parent child Browse files
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 &middot; ${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 &raquo;
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-full-width">
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