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