##// END OF EJS Templates
system-settings: prepare to allow to create snapshots of full system settings...
marcink -
r280:136f6d39 default
parent child Browse files
Show More
@@ -0,0 +1,75 b''
1 <%
2 elems = [
3 ## general
4 (_('RhodeCode Enterprise version'), c.rhodecode_version, ''),
5 (_('Upgrade info endpoint'), c.rhodecode_update_url, ''),
6 (_('Configuration INI file'), c.rhodecode_config_ini, ''),
7 ## systems stats
8 (_('RhodeCode Enterprise Server IP'), c.server_ip, ''),
9 (_('RhodeCode Enterprise Server ID'), c.server_id, ''),
10 (_('Platform'), c.platform, ''),
11 (_('Uptime'), c.uptime_age, ''),
12 (_('Storage location'), c.storage, ''),
13 (_('Storage disk space'), "%s/%s, %s%% used%s" % (h.format_byte_size_binary(c.disk['used']), h.format_byte_size_binary(c.disk['total']),(c.disk['percent']), ' %s' % c.disk['error'] if 'error' in c.disk else ''), ''),
14
15 (_('Search index storage'), c.index_storage, ''),
16 (_('Search index size'), "%s %s" % (h.format_byte_size_binary(c.disk_index['used']), ' %s' % c.disk_index['error'] if 'error' in c.disk_index else ''), ''),
17
18 (_('Gist storage'), c.gist_storage, ''),
19 (_('Gist storage size'), "%s (%s items)%s" % (h.format_byte_size_binary(c.disk_gist['used']),c.disk_gist['items'], ' %s' % c.disk_gist['error'] if 'error' in c.disk_gist else ''), ''),
20
21 (_('Archive cache'), c.archive_storage, ''),
22 (_('Archive cache size'), "%s%s" % (h.format_byte_size_binary(c.disk_archive['used']), ' %s' % c.disk_archive['error'] if 'error' in c.disk_archive else ''), ''),
23
24 (_('System memory'), c.system_memory, ''),
25 (_('CPU'), '%s %%' %(c.cpu), ''),
26 (_('Load'), '1min: %s, 5min: %s, 15min: %s' %(c.load['1_min'],c.load['5_min'],c.load['15_min']), ''),
27
28 ## rhodecode stuff
29 (_('Python version'), c.py_version, ''),
30 (_('Python path'), c.py_path, ''),
31 (_('GIT version'), c.git_version, ''),
32 (_('HG version'), c.hg_version, ''),
33 (_('SVN version'), c.svn_version, ''),
34 (_('Database'), "%s @ version: %s" % (c.db_type, c.db_migrate_version), ''),
35 (_('Database version'), c.db_version, ''),
36
37 ]
38 %>
39
40 <pre>
41 SYSTEM INFO
42 -----------
43
44 % for dt, dd, tt in elems:
45 ${dt}: ${dd}
46 % endfor
47
48 PYTHON PACKAGES
49 ---------------
50
51 % for key, value in c.py_modules:
52 ${key}: ${value}
53 % endfor
54
55 SYSTEM SETTINGS
56 ---------------
57
58 % for key, value in sorted(c.rhodecode_ini_safe.items()):
59 % if isinstance(value, dict):
60
61 % for key2, value2 in value.items():
62 [${key}]${key2}: ${value2}
63 % endfor
64
65 % else:
66 ${key}: ${value}
67 % endif
68 % endfor
69
70 </pre>
71
72
73
74
75
@@ -1,854 +1,885 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 """
22 """
23 settings controller for rhodecode admin
23 settings controller for rhodecode admin
24 """
24 """
25
25
26 import collections
26 import collections
27 import logging
27 import logging
28 import urllib2
28 import urllib2
29
29
30 import datetime
30 import datetime
31 import formencode
31 import formencode
32 from formencode import htmlfill
32 from formencode import htmlfill
33 import packaging.version
33 import packaging.version
34 from pylons import request, tmpl_context as c, url, config
34 from pylons import request, tmpl_context as c, url, config
35 from pylons.controllers.util import redirect
35 from pylons.controllers.util import redirect
36 from pylons.i18n.translation import _, lazy_ugettext
36 from pylons.i18n.translation import _, lazy_ugettext
37 from webob.exc import HTTPBadRequest
37 from webob.exc import HTTPBadRequest
38
38
39 import rhodecode
39 import rhodecode
40 from rhodecode.lib import auth
40 from rhodecode.lib import auth
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
42 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
43 from rhodecode.lib.base import BaseController, render
43 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.celerylib import tasks, run_task
44 from rhodecode.lib.celerylib import tasks, run_task
45 from rhodecode.lib.utils import repo2db_mapper
45 from rhodecode.lib.utils import repo2db_mapper
46 from rhodecode.lib.utils2 import (
46 from rhodecode.lib.utils2 import (
47 str2bool, safe_unicode, AttributeDict, safe_int)
47 str2bool, safe_unicode, AttributeDict, safe_int)
48 from rhodecode.lib.compat import OrderedDict
48 from rhodecode.lib.compat import OrderedDict
49 from rhodecode.lib.ext_json import json
49 from rhodecode.lib.ext_json import json
50 from rhodecode.lib.utils import jsonify
50 from rhodecode.lib.utils import jsonify
51
51
52 from rhodecode.model.db import RhodeCodeUi, Repository
52 from rhodecode.model.db import RhodeCodeUi, Repository, User
53 from rhodecode.model.forms import ApplicationSettingsForm, \
53 from rhodecode.model.forms import ApplicationSettingsForm, \
54 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
54 ApplicationUiSettingsForm, ApplicationVisualisationForm, \
55 LabsSettingsForm, IssueTrackerPatternsForm
55 LabsSettingsForm, IssueTrackerPatternsForm
56
56
57 from rhodecode.model.scm import ScmModel
57 from rhodecode.model.scm import ScmModel
58 from rhodecode.model.notification import EmailNotificationModel
58 from rhodecode.model.notification import EmailNotificationModel
59 from rhodecode.model.meta import Session
59 from rhodecode.model.meta import Session
60 from rhodecode.model.settings import (
60 from rhodecode.model.settings import (
61 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
61 IssueTrackerSettingsModel, VcsSettingsModel, SettingNotFound,
62 SettingsModel)
62 SettingsModel)
63
63 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
64 from rhodecode.model.supervisor import SupervisorModel, SUPERVISOR_MASTER
64
65
65
66
66 log = logging.getLogger(__name__)
67 log = logging.getLogger(__name__)
67
68
68
69
69 class SettingsController(BaseController):
70 class SettingsController(BaseController):
70 """REST Controller styled on the Atom Publishing Protocol"""
71 """REST Controller styled on the Atom Publishing Protocol"""
71 # To properly map this controller, ensure your config/routing.py
72 # To properly map this controller, ensure your config/routing.py
72 # file has a resource setup:
73 # file has a resource setup:
73 # map.resource('setting', 'settings', controller='admin/settings',
74 # map.resource('setting', 'settings', controller='admin/settings',
74 # path_prefix='/admin', name_prefix='admin_')
75 # path_prefix='/admin', name_prefix='admin_')
75
76
76 @LoginRequired()
77 @LoginRequired()
77 def __before__(self):
78 def __before__(self):
78 super(SettingsController, self).__before__()
79 super(SettingsController, self).__before__()
79 c.labs_active = str2bool(
80 c.labs_active = str2bool(
80 rhodecode.CONFIG.get('labs_settings_active', 'false'))
81 rhodecode.CONFIG.get('labs_settings_active', 'false'))
81 c.navlist = navigation.get_navlist(request)
82 c.navlist = navigation.get_navlist(request)
82
83
83 def _get_hg_ui_settings(self):
84 def _get_hg_ui_settings(self):
84 ret = RhodeCodeUi.query().all()
85 ret = RhodeCodeUi.query().all()
85
86
86 if not ret:
87 if not ret:
87 raise Exception('Could not get application ui settings !')
88 raise Exception('Could not get application ui settings !')
88 settings = {}
89 settings = {}
89 for each in ret:
90 for each in ret:
90 k = each.ui_key
91 k = each.ui_key
91 v = each.ui_value
92 v = each.ui_value
92 if k == '/':
93 if k == '/':
93 k = 'root_path'
94 k = 'root_path'
94
95
95 if k in ['push_ssl', 'publish']:
96 if k in ['push_ssl', 'publish']:
96 v = str2bool(v)
97 v = str2bool(v)
97
98
98 if k.find('.') != -1:
99 if k.find('.') != -1:
99 k = k.replace('.', '_')
100 k = k.replace('.', '_')
100
101
101 if each.ui_section in ['hooks', 'extensions']:
102 if each.ui_section in ['hooks', 'extensions']:
102 v = each.ui_active
103 v = each.ui_active
103
104
104 settings[each.ui_section + '_' + k] = v
105 settings[each.ui_section + '_' + k] = v
105 return settings
106 return settings
106
107
107 @HasPermissionAllDecorator('hg.admin')
108 @HasPermissionAllDecorator('hg.admin')
108 @auth.CSRFRequired()
109 @auth.CSRFRequired()
109 @jsonify
110 @jsonify
110 def delete_svn_pattern(self):
111 def delete_svn_pattern(self):
111 if not request.is_xhr:
112 if not request.is_xhr:
112 raise HTTPBadRequest()
113 raise HTTPBadRequest()
113
114
114 delete_pattern_id = request.POST.get('delete_svn_pattern')
115 delete_pattern_id = request.POST.get('delete_svn_pattern')
115 model = VcsSettingsModel()
116 model = VcsSettingsModel()
116 try:
117 try:
117 model.delete_global_svn_pattern(delete_pattern_id)
118 model.delete_global_svn_pattern(delete_pattern_id)
118 except SettingNotFound:
119 except SettingNotFound:
119 raise HTTPBadRequest()
120 raise HTTPBadRequest()
120
121
121 Session().commit()
122 Session().commit()
122 return True
123 return True
123
124
124 @HasPermissionAllDecorator('hg.admin')
125 @HasPermissionAllDecorator('hg.admin')
125 @auth.CSRFRequired()
126 @auth.CSRFRequired()
126 def settings_vcs_update(self):
127 def settings_vcs_update(self):
127 """POST /admin/settings: All items in the collection"""
128 """POST /admin/settings: All items in the collection"""
128 # url('admin_settings_vcs')
129 # url('admin_settings_vcs')
129 c.active = 'vcs'
130 c.active = 'vcs'
130
131
131 model = VcsSettingsModel()
132 model = VcsSettingsModel()
132 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
133 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
133 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
134 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
134
135
135 application_form = ApplicationUiSettingsForm()()
136 application_form = ApplicationUiSettingsForm()()
136 try:
137 try:
137 form_result = application_form.to_python(dict(request.POST))
138 form_result = application_form.to_python(dict(request.POST))
138 except formencode.Invalid as errors:
139 except formencode.Invalid as errors:
139 h.flash(
140 h.flash(
140 _("Some form inputs contain invalid data."),
141 _("Some form inputs contain invalid data."),
141 category='error')
142 category='error')
142 return htmlfill.render(
143 return htmlfill.render(
143 render('admin/settings/settings.html'),
144 render('admin/settings/settings.html'),
144 defaults=errors.value,
145 defaults=errors.value,
145 errors=errors.error_dict or {},
146 errors=errors.error_dict or {},
146 prefix_error=False,
147 prefix_error=False,
147 encoding="UTF-8",
148 encoding="UTF-8",
148 force_defaults=False
149 force_defaults=False
149 )
150 )
150
151
151 try:
152 try:
152 model.update_global_ssl_setting(form_result['web_push_ssl'])
153 model.update_global_ssl_setting(form_result['web_push_ssl'])
153 if c.visual.allow_repo_location_change:
154 if c.visual.allow_repo_location_change:
154 model.update_global_path_setting(
155 model.update_global_path_setting(
155 form_result['paths_root_path'])
156 form_result['paths_root_path'])
156 model.update_global_hook_settings(form_result)
157 model.update_global_hook_settings(form_result)
157 model.create_global_svn_settings(form_result)
158 model.create_global_svn_settings(form_result)
158 model.create_or_update_global_hg_settings(form_result)
159 model.create_or_update_global_hg_settings(form_result)
159 model.create_or_update_global_pr_settings(form_result)
160 model.create_or_update_global_pr_settings(form_result)
160 except Exception:
161 except Exception:
161 log.exception("Exception while updating settings")
162 log.exception("Exception while updating settings")
162 h.flash(_('Error occurred during updating '
163 h.flash(_('Error occurred during updating '
163 'application settings'), category='error')
164 'application settings'), category='error')
164 else:
165 else:
165 Session().commit()
166 Session().commit()
166 h.flash(_('Updated VCS settings'), category='success')
167 h.flash(_('Updated VCS settings'), category='success')
167 return redirect(url('admin_settings_vcs'))
168 return redirect(url('admin_settings_vcs'))
168
169
169 return htmlfill.render(
170 return htmlfill.render(
170 render('admin/settings/settings.html'),
171 render('admin/settings/settings.html'),
171 defaults=self._form_defaults(),
172 defaults=self._form_defaults(),
172 encoding="UTF-8",
173 encoding="UTF-8",
173 force_defaults=False)
174 force_defaults=False)
174
175
175 @HasPermissionAllDecorator('hg.admin')
176 @HasPermissionAllDecorator('hg.admin')
176 def settings_vcs(self):
177 def settings_vcs(self):
177 """GET /admin/settings: All items in the collection"""
178 """GET /admin/settings: All items in the collection"""
178 # url('admin_settings_vcs')
179 # url('admin_settings_vcs')
179 c.active = 'vcs'
180 c.active = 'vcs'
180 model = VcsSettingsModel()
181 model = VcsSettingsModel()
181 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
182 c.svn_branch_patterns = model.get_global_svn_branch_patterns()
182 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
183 c.svn_tag_patterns = model.get_global_svn_tag_patterns()
183
184
184 return htmlfill.render(
185 return htmlfill.render(
185 render('admin/settings/settings.html'),
186 render('admin/settings/settings.html'),
186 defaults=self._form_defaults(),
187 defaults=self._form_defaults(),
187 encoding="UTF-8",
188 encoding="UTF-8",
188 force_defaults=False)
189 force_defaults=False)
189
190
190 @HasPermissionAllDecorator('hg.admin')
191 @HasPermissionAllDecorator('hg.admin')
191 @auth.CSRFRequired()
192 @auth.CSRFRequired()
192 def settings_mapping_update(self):
193 def settings_mapping_update(self):
193 """POST /admin/settings/mapping: All items in the collection"""
194 """POST /admin/settings/mapping: All items in the collection"""
194 # url('admin_settings_mapping')
195 # url('admin_settings_mapping')
195 c.active = 'mapping'
196 c.active = 'mapping'
196 rm_obsolete = request.POST.get('destroy', False)
197 rm_obsolete = request.POST.get('destroy', False)
197 invalidate_cache = request.POST.get('invalidate', False)
198 invalidate_cache = request.POST.get('invalidate', False)
198 log.debug(
199 log.debug(
199 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
200 'rescanning repo location with destroy obsolete=%s', rm_obsolete)
200
201
201 if invalidate_cache:
202 if invalidate_cache:
202 log.debug('invalidating all repositories cache')
203 log.debug('invalidating all repositories cache')
203 for repo in Repository.get_all():
204 for repo in Repository.get_all():
204 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
205 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
205
206
206 filesystem_repos = ScmModel().repo_scan()
207 filesystem_repos = ScmModel().repo_scan()
207 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
208 added, removed = repo2db_mapper(filesystem_repos, rm_obsolete)
208 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
209 _repr = lambda l: ', '.join(map(safe_unicode, l)) or '-'
209 h.flash(_('Repositories successfully '
210 h.flash(_('Repositories successfully '
210 'rescanned added: %s ; removed: %s') %
211 'rescanned added: %s ; removed: %s') %
211 (_repr(added), _repr(removed)),
212 (_repr(added), _repr(removed)),
212 category='success')
213 category='success')
213 return redirect(url('admin_settings_mapping'))
214 return redirect(url('admin_settings_mapping'))
214
215
215 @HasPermissionAllDecorator('hg.admin')
216 @HasPermissionAllDecorator('hg.admin')
216 def settings_mapping(self):
217 def settings_mapping(self):
217 """GET /admin/settings/mapping: All items in the collection"""
218 """GET /admin/settings/mapping: All items in the collection"""
218 # url('admin_settings_mapping')
219 # url('admin_settings_mapping')
219 c.active = 'mapping'
220 c.active = 'mapping'
220
221
221 return htmlfill.render(
222 return htmlfill.render(
222 render('admin/settings/settings.html'),
223 render('admin/settings/settings.html'),
223 defaults=self._form_defaults(),
224 defaults=self._form_defaults(),
224 encoding="UTF-8",
225 encoding="UTF-8",
225 force_defaults=False)
226 force_defaults=False)
226
227
227 @HasPermissionAllDecorator('hg.admin')
228 @HasPermissionAllDecorator('hg.admin')
228 @auth.CSRFRequired()
229 @auth.CSRFRequired()
229 def settings_global_update(self):
230 def settings_global_update(self):
230 """POST /admin/settings/global: All items in the collection"""
231 """POST /admin/settings/global: All items in the collection"""
231 # url('admin_settings_global')
232 # url('admin_settings_global')
232 c.active = 'global'
233 c.active = 'global'
233 application_form = ApplicationSettingsForm()()
234 application_form = ApplicationSettingsForm()()
234 try:
235 try:
235 form_result = application_form.to_python(dict(request.POST))
236 form_result = application_form.to_python(dict(request.POST))
236 except formencode.Invalid as errors:
237 except formencode.Invalid as errors:
237 return htmlfill.render(
238 return htmlfill.render(
238 render('admin/settings/settings.html'),
239 render('admin/settings/settings.html'),
239 defaults=errors.value,
240 defaults=errors.value,
240 errors=errors.error_dict or {},
241 errors=errors.error_dict or {},
241 prefix_error=False,
242 prefix_error=False,
242 encoding="UTF-8",
243 encoding="UTF-8",
243 force_defaults=False)
244 force_defaults=False)
244
245
245 try:
246 try:
246 settings = [
247 settings = [
247 ('title', 'rhodecode_title'),
248 ('title', 'rhodecode_title'),
248 ('realm', 'rhodecode_realm'),
249 ('realm', 'rhodecode_realm'),
249 ('pre_code', 'rhodecode_pre_code'),
250 ('pre_code', 'rhodecode_pre_code'),
250 ('post_code', 'rhodecode_post_code'),
251 ('post_code', 'rhodecode_post_code'),
251 ('captcha_public_key', 'rhodecode_captcha_public_key'),
252 ('captcha_public_key', 'rhodecode_captcha_public_key'),
252 ('captcha_private_key', 'rhodecode_captcha_private_key'),
253 ('captcha_private_key', 'rhodecode_captcha_private_key'),
253 ]
254 ]
254 for setting, form_key in settings:
255 for setting, form_key in settings:
255 sett = SettingsModel().create_or_update_setting(
256 sett = SettingsModel().create_or_update_setting(
256 setting, form_result[form_key])
257 setting, form_result[form_key])
257 Session().add(sett)
258 Session().add(sett)
258
259
259 Session().commit()
260 Session().commit()
260 SettingsModel().invalidate_settings_cache()
261 SettingsModel().invalidate_settings_cache()
261 h.flash(_('Updated application settings'), category='success')
262 h.flash(_('Updated application settings'), category='success')
262 except Exception:
263 except Exception:
263 log.exception("Exception while updating application settings")
264 log.exception("Exception while updating application settings")
264 h.flash(
265 h.flash(
265 _('Error occurred during updating application settings'),
266 _('Error occurred during updating application settings'),
266 category='error')
267 category='error')
267
268
268 return redirect(url('admin_settings_global'))
269 return redirect(url('admin_settings_global'))
269
270
270 @HasPermissionAllDecorator('hg.admin')
271 @HasPermissionAllDecorator('hg.admin')
271 def settings_global(self):
272 def settings_global(self):
272 """GET /admin/settings/global: All items in the collection"""
273 """GET /admin/settings/global: All items in the collection"""
273 # url('admin_settings_global')
274 # url('admin_settings_global')
274 c.active = 'global'
275 c.active = 'global'
275
276
276 return htmlfill.render(
277 return htmlfill.render(
277 render('admin/settings/settings.html'),
278 render('admin/settings/settings.html'),
278 defaults=self._form_defaults(),
279 defaults=self._form_defaults(),
279 encoding="UTF-8",
280 encoding="UTF-8",
280 force_defaults=False)
281 force_defaults=False)
281
282
282 @HasPermissionAllDecorator('hg.admin')
283 @HasPermissionAllDecorator('hg.admin')
283 @auth.CSRFRequired()
284 @auth.CSRFRequired()
284 def settings_visual_update(self):
285 def settings_visual_update(self):
285 """POST /admin/settings/visual: All items in the collection"""
286 """POST /admin/settings/visual: All items in the collection"""
286 # url('admin_settings_visual')
287 # url('admin_settings_visual')
287 c.active = 'visual'
288 c.active = 'visual'
288 application_form = ApplicationVisualisationForm()()
289 application_form = ApplicationVisualisationForm()()
289 try:
290 try:
290 form_result = application_form.to_python(dict(request.POST))
291 form_result = application_form.to_python(dict(request.POST))
291 except formencode.Invalid as errors:
292 except formencode.Invalid as errors:
292 return htmlfill.render(
293 return htmlfill.render(
293 render('admin/settings/settings.html'),
294 render('admin/settings/settings.html'),
294 defaults=errors.value,
295 defaults=errors.value,
295 errors=errors.error_dict or {},
296 errors=errors.error_dict or {},
296 prefix_error=False,
297 prefix_error=False,
297 encoding="UTF-8",
298 encoding="UTF-8",
298 force_defaults=False
299 force_defaults=False
299 )
300 )
300
301
301 try:
302 try:
302 settings = [
303 settings = [
303 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
304 ('show_public_icon', 'rhodecode_show_public_icon', 'bool'),
304 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
305 ('show_private_icon', 'rhodecode_show_private_icon', 'bool'),
305 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
306 ('stylify_metatags', 'rhodecode_stylify_metatags', 'bool'),
306 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
307 ('repository_fields', 'rhodecode_repository_fields', 'bool'),
307 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
308 ('dashboard_items', 'rhodecode_dashboard_items', 'int'),
308 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
309 ('admin_grid_items', 'rhodecode_admin_grid_items', 'int'),
309 ('show_version', 'rhodecode_show_version', 'bool'),
310 ('show_version', 'rhodecode_show_version', 'bool'),
310 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
311 ('use_gravatar', 'rhodecode_use_gravatar', 'bool'),
311 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
312 ('markup_renderer', 'rhodecode_markup_renderer', 'unicode'),
312 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
313 ('gravatar_url', 'rhodecode_gravatar_url', 'unicode'),
313 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
314 ('clone_uri_tmpl', 'rhodecode_clone_uri_tmpl', 'unicode'),
314 ('support_url', 'rhodecode_support_url', 'unicode'),
315 ('support_url', 'rhodecode_support_url', 'unicode'),
315 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
316 ('show_revision_number', 'rhodecode_show_revision_number', 'bool'),
316 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
317 ('show_sha_length', 'rhodecode_show_sha_length', 'int'),
317 ]
318 ]
318 for setting, form_key, type_ in settings:
319 for setting, form_key, type_ in settings:
319 sett = SettingsModel().create_or_update_setting(
320 sett = SettingsModel().create_or_update_setting(
320 setting, form_result[form_key], type_)
321 setting, form_result[form_key], type_)
321 Session().add(sett)
322 Session().add(sett)
322
323
323 Session().commit()
324 Session().commit()
324 SettingsModel().invalidate_settings_cache()
325 SettingsModel().invalidate_settings_cache()
325 h.flash(_('Updated visualisation settings'), category='success')
326 h.flash(_('Updated visualisation settings'), category='success')
326 except Exception:
327 except Exception:
327 log.exception("Exception updating visualization settings")
328 log.exception("Exception updating visualization settings")
328 h.flash(_('Error occurred during updating '
329 h.flash(_('Error occurred during updating '
329 'visualisation settings'),
330 'visualisation settings'),
330 category='error')
331 category='error')
331
332
332 return redirect(url('admin_settings_visual'))
333 return redirect(url('admin_settings_visual'))
333
334
334 @HasPermissionAllDecorator('hg.admin')
335 @HasPermissionAllDecorator('hg.admin')
335 def settings_visual(self):
336 def settings_visual(self):
336 """GET /admin/settings/visual: All items in the collection"""
337 """GET /admin/settings/visual: All items in the collection"""
337 # url('admin_settings_visual')
338 # url('admin_settings_visual')
338 c.active = 'visual'
339 c.active = 'visual'
339
340
340 return htmlfill.render(
341 return htmlfill.render(
341 render('admin/settings/settings.html'),
342 render('admin/settings/settings.html'),
342 defaults=self._form_defaults(),
343 defaults=self._form_defaults(),
343 encoding="UTF-8",
344 encoding="UTF-8",
344 force_defaults=False)
345 force_defaults=False)
345
346
346 @HasPermissionAllDecorator('hg.admin')
347 @HasPermissionAllDecorator('hg.admin')
347 @auth.CSRFRequired()
348 @auth.CSRFRequired()
348 def settings_issuetracker_test(self):
349 def settings_issuetracker_test(self):
349 if request.is_xhr:
350 if request.is_xhr:
350 return h.urlify_commit_message(
351 return h.urlify_commit_message(
351 request.POST.get('test_text', ''),
352 request.POST.get('test_text', ''),
352 'repo_group/test_repo1')
353 'repo_group/test_repo1')
353 else:
354 else:
354 raise HTTPBadRequest()
355 raise HTTPBadRequest()
355
356
356 @HasPermissionAllDecorator('hg.admin')
357 @HasPermissionAllDecorator('hg.admin')
357 @auth.CSRFRequired()
358 @auth.CSRFRequired()
358 def settings_issuetracker_delete(self):
359 def settings_issuetracker_delete(self):
359 uid = request.POST.get('uid')
360 uid = request.POST.get('uid')
360 IssueTrackerSettingsModel().delete_entries(uid)
361 IssueTrackerSettingsModel().delete_entries(uid)
361 h.flash(_('Removed issue tracker entry'), category='success')
362 h.flash(_('Removed issue tracker entry'), category='success')
362 return redirect(url('admin_settings_issuetracker'))
363 return redirect(url('admin_settings_issuetracker'))
363
364
364 @HasPermissionAllDecorator('hg.admin')
365 @HasPermissionAllDecorator('hg.admin')
365 def settings_issuetracker(self):
366 def settings_issuetracker(self):
366 """GET /admin/settings/issue-tracker: All items in the collection"""
367 """GET /admin/settings/issue-tracker: All items in the collection"""
367 # url('admin_settings_issuetracker')
368 # url('admin_settings_issuetracker')
368 c.active = 'issuetracker'
369 c.active = 'issuetracker'
369 defaults = SettingsModel().get_all_settings()
370 defaults = SettingsModel().get_all_settings()
370
371
371 entry_key = 'rhodecode_issuetracker_pat_'
372 entry_key = 'rhodecode_issuetracker_pat_'
372
373
373 c.issuetracker_entries = {}
374 c.issuetracker_entries = {}
374 for k, v in defaults.items():
375 for k, v in defaults.items():
375 if k.startswith(entry_key):
376 if k.startswith(entry_key):
376 uid = k[len(entry_key):]
377 uid = k[len(entry_key):]
377 c.issuetracker_entries[uid] = None
378 c.issuetracker_entries[uid] = None
378
379
379 for uid in c.issuetracker_entries:
380 for uid in c.issuetracker_entries:
380 c.issuetracker_entries[uid] = AttributeDict({
381 c.issuetracker_entries[uid] = AttributeDict({
381 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
382 'pat': defaults.get('rhodecode_issuetracker_pat_' + uid),
382 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
383 'url': defaults.get('rhodecode_issuetracker_url_' + uid),
383 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
384 'pref': defaults.get('rhodecode_issuetracker_pref_' + uid),
384 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
385 'desc': defaults.get('rhodecode_issuetracker_desc_' + uid),
385 })
386 })
386
387
387 return render('admin/settings/settings.html')
388 return render('admin/settings/settings.html')
388
389
389 @HasPermissionAllDecorator('hg.admin')
390 @HasPermissionAllDecorator('hg.admin')
390 @auth.CSRFRequired()
391 @auth.CSRFRequired()
391 def settings_issuetracker_save(self):
392 def settings_issuetracker_save(self):
392 settings_model = IssueTrackerSettingsModel()
393 settings_model = IssueTrackerSettingsModel()
393
394
394 form = IssueTrackerPatternsForm()().to_python(request.POST)
395 form = IssueTrackerPatternsForm()().to_python(request.POST)
395 for uid in form['delete_patterns']:
396 for uid in form['delete_patterns']:
396 settings_model.delete_entries(uid)
397 settings_model.delete_entries(uid)
397
398
398 for pattern in form['patterns']:
399 for pattern in form['patterns']:
399 for setting, value, type_ in pattern:
400 for setting, value, type_ in pattern:
400 sett = settings_model.create_or_update_setting(
401 sett = settings_model.create_or_update_setting(
401 setting, value, type_)
402 setting, value, type_)
402 Session().add(sett)
403 Session().add(sett)
403
404
404 Session().commit()
405 Session().commit()
405
406
406 SettingsModel().invalidate_settings_cache()
407 SettingsModel().invalidate_settings_cache()
407 h.flash(_('Updated issue tracker entries'), category='success')
408 h.flash(_('Updated issue tracker entries'), category='success')
408 return redirect(url('admin_settings_issuetracker'))
409 return redirect(url('admin_settings_issuetracker'))
409
410
410 @HasPermissionAllDecorator('hg.admin')
411 @HasPermissionAllDecorator('hg.admin')
411 @auth.CSRFRequired()
412 @auth.CSRFRequired()
412 def settings_email_update(self):
413 def settings_email_update(self):
413 """POST /admin/settings/email: All items in the collection"""
414 """POST /admin/settings/email: All items in the collection"""
414 # url('admin_settings_email')
415 # url('admin_settings_email')
415 c.active = 'email'
416 c.active = 'email'
416
417
417 test_email = request.POST.get('test_email')
418 test_email = request.POST.get('test_email')
418
419
419 if not test_email:
420 if not test_email:
420 h.flash(_('Please enter email address'), category='error')
421 h.flash(_('Please enter email address'), category='error')
421 return redirect(url('admin_settings_email'))
422 return redirect(url('admin_settings_email'))
422
423
423 email_kwargs = {
424 email_kwargs = {
424 'date': datetime.datetime.now(),
425 'date': datetime.datetime.now(),
425 'user': c.rhodecode_user,
426 'user': c.rhodecode_user,
426 'rhodecode_version': c.rhodecode_version
427 'rhodecode_version': c.rhodecode_version
427 }
428 }
428
429
429 (subject, headers, email_body,
430 (subject, headers, email_body,
430 email_body_plaintext) = EmailNotificationModel().render_email(
431 email_body_plaintext) = EmailNotificationModel().render_email(
431 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
432 EmailNotificationModel.TYPE_EMAIL_TEST, **email_kwargs)
432
433
433 recipients = [test_email] if test_email else None
434 recipients = [test_email] if test_email else None
434
435
435 run_task(tasks.send_email, recipients, subject,
436 run_task(tasks.send_email, recipients, subject,
436 email_body_plaintext, email_body)
437 email_body_plaintext, email_body)
437
438
438 h.flash(_('Send email task created'), category='success')
439 h.flash(_('Send email task created'), category='success')
439 return redirect(url('admin_settings_email'))
440 return redirect(url('admin_settings_email'))
440
441
441 @HasPermissionAllDecorator('hg.admin')
442 @HasPermissionAllDecorator('hg.admin')
442 def settings_email(self):
443 def settings_email(self):
443 """GET /admin/settings/email: All items in the collection"""
444 """GET /admin/settings/email: All items in the collection"""
444 # url('admin_settings_email')
445 # url('admin_settings_email')
445 c.active = 'email'
446 c.active = 'email'
446 c.rhodecode_ini = rhodecode.CONFIG
447 c.rhodecode_ini = rhodecode.CONFIG
447
448
448 return htmlfill.render(
449 return htmlfill.render(
449 render('admin/settings/settings.html'),
450 render('admin/settings/settings.html'),
450 defaults=self._form_defaults(),
451 defaults=self._form_defaults(),
451 encoding="UTF-8",
452 encoding="UTF-8",
452 force_defaults=False)
453 force_defaults=False)
453
454
454 @HasPermissionAllDecorator('hg.admin')
455 @HasPermissionAllDecorator('hg.admin')
455 @auth.CSRFRequired()
456 @auth.CSRFRequired()
456 def settings_hooks_update(self):
457 def settings_hooks_update(self):
457 """POST or DELETE /admin/settings/hooks: All items in the collection"""
458 """POST or DELETE /admin/settings/hooks: All items in the collection"""
458 # url('admin_settings_hooks')
459 # url('admin_settings_hooks')
459 c.active = 'hooks'
460 c.active = 'hooks'
460 if c.visual.allow_custom_hooks_settings:
461 if c.visual.allow_custom_hooks_settings:
461 ui_key = request.POST.get('new_hook_ui_key')
462 ui_key = request.POST.get('new_hook_ui_key')
462 ui_value = request.POST.get('new_hook_ui_value')
463 ui_value = request.POST.get('new_hook_ui_value')
463
464
464 hook_id = request.POST.get('hook_id')
465 hook_id = request.POST.get('hook_id')
465 new_hook = False
466 new_hook = False
466
467
467 model = SettingsModel()
468 model = SettingsModel()
468 try:
469 try:
469 if ui_value and ui_key:
470 if ui_value and ui_key:
470 model.create_or_update_hook(ui_key, ui_value)
471 model.create_or_update_hook(ui_key, ui_value)
471 h.flash(_('Added new hook'), category='success')
472 h.flash(_('Added new hook'), category='success')
472 new_hook = True
473 new_hook = True
473 elif hook_id:
474 elif hook_id:
474 RhodeCodeUi.delete(hook_id)
475 RhodeCodeUi.delete(hook_id)
475 Session().commit()
476 Session().commit()
476
477
477 # check for edits
478 # check for edits
478 update = False
479 update = False
479 _d = request.POST.dict_of_lists()
480 _d = request.POST.dict_of_lists()
480 for k, v in zip(_d.get('hook_ui_key', []),
481 for k, v in zip(_d.get('hook_ui_key', []),
481 _d.get('hook_ui_value_new', [])):
482 _d.get('hook_ui_value_new', [])):
482 model.create_or_update_hook(k, v)
483 model.create_or_update_hook(k, v)
483 update = True
484 update = True
484
485
485 if update and not new_hook:
486 if update and not new_hook:
486 h.flash(_('Updated hooks'), category='success')
487 h.flash(_('Updated hooks'), category='success')
487 Session().commit()
488 Session().commit()
488 except Exception:
489 except Exception:
489 log.exception("Exception during hook creation")
490 log.exception("Exception during hook creation")
490 h.flash(_('Error occurred during hook creation'),
491 h.flash(_('Error occurred during hook creation'),
491 category='error')
492 category='error')
492
493
493 return redirect(url('admin_settings_hooks'))
494 return redirect(url('admin_settings_hooks'))
494
495
495 @HasPermissionAllDecorator('hg.admin')
496 @HasPermissionAllDecorator('hg.admin')
496 def settings_hooks(self):
497 def settings_hooks(self):
497 """GET /admin/settings/hooks: All items in the collection"""
498 """GET /admin/settings/hooks: All items in the collection"""
498 # url('admin_settings_hooks')
499 # url('admin_settings_hooks')
499 c.active = 'hooks'
500 c.active = 'hooks'
500
501
501 model = SettingsModel()
502 model = SettingsModel()
502 c.hooks = model.get_builtin_hooks()
503 c.hooks = model.get_builtin_hooks()
503 c.custom_hooks = model.get_custom_hooks()
504 c.custom_hooks = model.get_custom_hooks()
504
505
505 return htmlfill.render(
506 return htmlfill.render(
506 render('admin/settings/settings.html'),
507 render('admin/settings/settings.html'),
507 defaults=self._form_defaults(),
508 defaults=self._form_defaults(),
508 encoding="UTF-8",
509 encoding="UTF-8",
509 force_defaults=False)
510 force_defaults=False)
510
511
511 @HasPermissionAllDecorator('hg.admin')
512 @HasPermissionAllDecorator('hg.admin')
512 def settings_search(self):
513 def settings_search(self):
513 """GET /admin/settings/search: All items in the collection"""
514 """GET /admin/settings/search: All items in the collection"""
514 # url('admin_settings_search')
515 # url('admin_settings_search')
515 c.active = 'search'
516 c.active = 'search'
516
517
517 from rhodecode.lib.index import searcher_from_config
518 from rhodecode.lib.index import searcher_from_config
518 searcher = searcher_from_config(config)
519 searcher = searcher_from_config(config)
519 c.statistics = searcher.statistics()
520 c.statistics = searcher.statistics()
520
521
521 return render('admin/settings/settings.html')
522 return render('admin/settings/settings.html')
522
523
523 @HasPermissionAllDecorator('hg.admin')
524 @HasPermissionAllDecorator('hg.admin')
524 def settings_system(self):
525 def settings_system(self):
525 """GET /admin/settings/system: All items in the collection"""
526 """GET /admin/settings/system: All items in the collection"""
526 # url('admin_settings_system')
527 # url('admin_settings_system')
528 snapshot = str2bool(request.GET.get('snapshot'))
527 c.active = 'system'
529 c.active = 'system'
528
530
529 defaults = self._form_defaults()
531 defaults = self._form_defaults()
530 c.rhodecode_ini = rhodecode.CONFIG
532 c.rhodecode_ini = rhodecode.CONFIG
531 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
533 c.rhodecode_update_url = defaults.get('rhodecode_update_url')
532 server_info = ScmModel().get_server_info(request.environ)
534 server_info = ScmModel().get_server_info(request.environ)
533 for key, val in server_info.iteritems():
535 for key, val in server_info.iteritems():
534 setattr(c, key, val)
536 setattr(c, key, val)
535
537
536 if c.disk['percent'] > 90:
538 if c.disk['percent'] > 90:
537 h.flash(h.literal(_(
539 h.flash(h.literal(_(
538 'Critical: your disk space is very low <b>%s%%</b> used' %
540 'Critical: your disk space is very low <b>%s%%</b> used' %
539 c.disk['percent'])), 'error')
541 c.disk['percent'])), 'error')
540 elif c.disk['percent'] > 70:
542 elif c.disk['percent'] > 70:
541 h.flash(h.literal(_(
543 h.flash(h.literal(_(
542 'Warning: your disk space is running low <b>%s%%</b> used' %
544 'Warning: your disk space is running low <b>%s%%</b> used' %
543 c.disk['percent'])), 'warning')
545 c.disk['percent'])), 'warning')
544
546
545 try:
547 try:
546 c.uptime_age = h._age(
548 c.uptime_age = h._age(
547 h.time_to_datetime(c.boot_time), False, show_suffix=False)
549 h.time_to_datetime(c.boot_time), False, show_suffix=False)
548 except TypeError:
550 except TypeError:
549 c.uptime_age = c.boot_time
551 c.uptime_age = c.boot_time
550
552
551 try:
553 try:
552 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
554 c.system_memory = '%s/%s, %s%% (%s%%) used%s' % (
553 h.format_byte_size_binary(c.memory['used']),
555 h.format_byte_size_binary(c.memory['used']),
554 h.format_byte_size_binary(c.memory['total']),
556 h.format_byte_size_binary(c.memory['total']),
555 c.memory['percent2'],
557 c.memory['percent2'],
556 c.memory['percent'],
558 c.memory['percent'],
557 ' %s' % c.memory['error'] if 'error' in c.memory else '')
559 ' %s' % c.memory['error'] if 'error' in c.memory else '')
558 except TypeError:
560 except TypeError:
559 c.system_memory = 'NOT AVAILABLE'
561 c.system_memory = 'NOT AVAILABLE'
560
562
563 rhodecode_ini_safe = rhodecode.CONFIG.copy()
564 blacklist = [
565 'rhodecode_license_key',
566 'routes.map',
567 'pylons.h',
568 'pylons.app_globals',
569 'pylons.environ_config',
570 'sqlalchemy.db1.url',
571 ('app_conf', 'sqlalchemy.db1.url')
572 ]
573 for k in blacklist:
574 if isinstance(k, tuple):
575 section, key = k
576 if section in rhodecode_ini_safe:
577 rhodecode_ini_safe[section].pop(key, None)
578 else:
579 rhodecode_ini_safe.pop(k, None)
580
581 c.rhodecode_ini_safe = rhodecode_ini_safe
582
583 # TODO: marcink, figure out how to allow only selected users to do this
584 c.allowed_to_snapshot = False
585
586 if snapshot:
587 if c.allowed_to_snapshot:
588 return render('admin/settings/settings_system_snapshot.html')
589 else:
590 h.flash('You are not allowed to do this', category='warning')
591
561 return htmlfill.render(
592 return htmlfill.render(
562 render('admin/settings/settings.html'),
593 render('admin/settings/settings.html'),
563 defaults=defaults,
594 defaults=defaults,
564 encoding="UTF-8",
595 encoding="UTF-8",
565 force_defaults=False)
596 force_defaults=False)
566
597
567 @staticmethod
598 @staticmethod
568 def get_update_data(update_url):
599 def get_update_data(update_url):
569 """Return the JSON update data."""
600 """Return the JSON update data."""
570 ver = rhodecode.__version__
601 ver = rhodecode.__version__
571 log.debug('Checking for upgrade on `%s` server', update_url)
602 log.debug('Checking for upgrade on `%s` server', update_url)
572 opener = urllib2.build_opener()
603 opener = urllib2.build_opener()
573 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
604 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
574 response = opener.open(update_url)
605 response = opener.open(update_url)
575 response_data = response.read()
606 response_data = response.read()
576 data = json.loads(response_data)
607 data = json.loads(response_data)
577
608
578 return data
609 return data
579
610
580 @HasPermissionAllDecorator('hg.admin')
611 @HasPermissionAllDecorator('hg.admin')
581 def settings_system_update(self):
612 def settings_system_update(self):
582 """GET /admin/settings/system/updates: All items in the collection"""
613 """GET /admin/settings/system/updates: All items in the collection"""
583 # url('admin_settings_system_update')
614 # url('admin_settings_system_update')
584 defaults = self._form_defaults()
615 defaults = self._form_defaults()
585 update_url = defaults.get('rhodecode_update_url', '')
616 update_url = defaults.get('rhodecode_update_url', '')
586
617
587 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
618 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">%s</div>' % (s)
588 try:
619 try:
589 data = self.get_update_data(update_url)
620 data = self.get_update_data(update_url)
590 except urllib2.URLError as e:
621 except urllib2.URLError as e:
591 log.exception("Exception contacting upgrade server")
622 log.exception("Exception contacting upgrade server")
592 return _err('Failed to contact upgrade server: %r' % e)
623 return _err('Failed to contact upgrade server: %r' % e)
593 except ValueError as e:
624 except ValueError as e:
594 log.exception("Bad data sent from update server")
625 log.exception("Bad data sent from update server")
595 return _err('Bad data sent from update server')
626 return _err('Bad data sent from update server')
596
627
597 latest = data['versions'][0]
628 latest = data['versions'][0]
598
629
599 c.update_url = update_url
630 c.update_url = update_url
600 c.latest_data = latest
631 c.latest_data = latest
601 c.latest_ver = latest['version']
632 c.latest_ver = latest['version']
602 c.cur_ver = rhodecode.__version__
633 c.cur_ver = rhodecode.__version__
603 c.should_upgrade = False
634 c.should_upgrade = False
604
635
605 if (packaging.version.Version(c.latest_ver) >
636 if (packaging.version.Version(c.latest_ver) >
606 packaging.version.Version(c.cur_ver)):
637 packaging.version.Version(c.cur_ver)):
607 c.should_upgrade = True
638 c.should_upgrade = True
608 c.important_notices = latest['general']
639 c.important_notices = latest['general']
609
640
610 return render('admin/settings/settings_system_update.html')
641 return render('admin/settings/settings_system_update.html')
611
642
612 @HasPermissionAllDecorator('hg.admin')
643 @HasPermissionAllDecorator('hg.admin')
613 def settings_supervisor(self):
644 def settings_supervisor(self):
614 c.rhodecode_ini = rhodecode.CONFIG
645 c.rhodecode_ini = rhodecode.CONFIG
615 c.active = 'supervisor'
646 c.active = 'supervisor'
616
647
617 c.supervisor_procs = OrderedDict([
648 c.supervisor_procs = OrderedDict([
618 (SUPERVISOR_MASTER, {}),
649 (SUPERVISOR_MASTER, {}),
619 ])
650 ])
620
651
621 c.log_size = 10240
652 c.log_size = 10240
622 supervisor = SupervisorModel()
653 supervisor = SupervisorModel()
623
654
624 _connection = supervisor.get_connection(
655 _connection = supervisor.get_connection(
625 c.rhodecode_ini.get('supervisor.uri'))
656 c.rhodecode_ini.get('supervisor.uri'))
626 c.connection_error = None
657 c.connection_error = None
627 try:
658 try:
628 _connection.supervisor.getAllProcessInfo()
659 _connection.supervisor.getAllProcessInfo()
629 except Exception as e:
660 except Exception as e:
630 c.connection_error = str(e)
661 c.connection_error = str(e)
631 log.exception("Exception reading supervisor data")
662 log.exception("Exception reading supervisor data")
632 return render('admin/settings/settings.html')
663 return render('admin/settings/settings.html')
633
664
634 groupid = c.rhodecode_ini.get('supervisor.group_id')
665 groupid = c.rhodecode_ini.get('supervisor.group_id')
635
666
636 # feed our group processes to the main
667 # feed our group processes to the main
637 for proc in supervisor.get_group_processes(_connection, groupid):
668 for proc in supervisor.get_group_processes(_connection, groupid):
638 c.supervisor_procs[proc['name']] = {}
669 c.supervisor_procs[proc['name']] = {}
639
670
640 for k in c.supervisor_procs.keys():
671 for k in c.supervisor_procs.keys():
641 try:
672 try:
642 # master process info
673 # master process info
643 if k == SUPERVISOR_MASTER:
674 if k == SUPERVISOR_MASTER:
644 _data = supervisor.get_master_state(_connection)
675 _data = supervisor.get_master_state(_connection)
645 _data['name'] = 'supervisor master'
676 _data['name'] = 'supervisor master'
646 _data['description'] = 'pid %s, id: %s, ver: %s' % (
677 _data['description'] = 'pid %s, id: %s, ver: %s' % (
647 _data['pid'], _data['id'], _data['ver'])
678 _data['pid'], _data['id'], _data['ver'])
648 c.supervisor_procs[k] = _data
679 c.supervisor_procs[k] = _data
649 else:
680 else:
650 procid = groupid + ":" + k
681 procid = groupid + ":" + k
651 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
682 c.supervisor_procs[k] = supervisor.get_process_info(_connection, procid)
652 except Exception as e:
683 except Exception as e:
653 log.exception("Exception reading supervisor data")
684 log.exception("Exception reading supervisor data")
654 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
685 c.supervisor_procs[k] = {'_rhodecode_error': str(e)}
655
686
656 return render('admin/settings/settings.html')
687 return render('admin/settings/settings.html')
657
688
658 @HasPermissionAllDecorator('hg.admin')
689 @HasPermissionAllDecorator('hg.admin')
659 def settings_supervisor_log(self, procid):
690 def settings_supervisor_log(self, procid):
660 import rhodecode
691 import rhodecode
661 c.rhodecode_ini = rhodecode.CONFIG
692 c.rhodecode_ini = rhodecode.CONFIG
662 c.active = 'supervisor_tail'
693 c.active = 'supervisor_tail'
663
694
664 supervisor = SupervisorModel()
695 supervisor = SupervisorModel()
665 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
696 _connection = supervisor.get_connection(c.rhodecode_ini.get('supervisor.uri'))
666 groupid = c.rhodecode_ini.get('supervisor.group_id')
697 groupid = c.rhodecode_ini.get('supervisor.group_id')
667 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
698 procid = groupid + ":" + procid if procid != SUPERVISOR_MASTER else procid
668
699
669 c.log_size = 10240
700 c.log_size = 10240
670 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
701 offset = abs(safe_int(request.GET.get('offset', c.log_size))) * -1
671 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
702 c.log = supervisor.read_process_log(_connection, procid, offset, 0)
672
703
673 return render('admin/settings/settings.html')
704 return render('admin/settings/settings.html')
674
705
675 @HasPermissionAllDecorator('hg.admin')
706 @HasPermissionAllDecorator('hg.admin')
676 @auth.CSRFRequired()
707 @auth.CSRFRequired()
677 def settings_labs_update(self):
708 def settings_labs_update(self):
678 """POST /admin/settings/labs: All items in the collection"""
709 """POST /admin/settings/labs: All items in the collection"""
679 # url('admin_settings/labs', method={'POST'})
710 # url('admin_settings/labs', method={'POST'})
680 c.active = 'labs'
711 c.active = 'labs'
681
712
682 application_form = LabsSettingsForm()()
713 application_form = LabsSettingsForm()()
683 try:
714 try:
684 form_result = application_form.to_python(dict(request.POST))
715 form_result = application_form.to_python(dict(request.POST))
685 except formencode.Invalid as errors:
716 except formencode.Invalid as errors:
686 h.flash(
717 h.flash(
687 _('Some form inputs contain invalid data.'),
718 _('Some form inputs contain invalid data.'),
688 category='error')
719 category='error')
689 return htmlfill.render(
720 return htmlfill.render(
690 render('admin/settings/settings.html'),
721 render('admin/settings/settings.html'),
691 defaults=errors.value,
722 defaults=errors.value,
692 errors=errors.error_dict or {},
723 errors=errors.error_dict or {},
693 prefix_error=False,
724 prefix_error=False,
694 encoding='UTF-8',
725 encoding='UTF-8',
695 force_defaults=False
726 force_defaults=False
696 )
727 )
697
728
698 try:
729 try:
699 session = Session()
730 session = Session()
700 for setting in _LAB_SETTINGS:
731 for setting in _LAB_SETTINGS:
701 setting_name = setting.key[len('rhodecode_'):]
732 setting_name = setting.key[len('rhodecode_'):]
702 sett = SettingsModel().create_or_update_setting(
733 sett = SettingsModel().create_or_update_setting(
703 setting_name, form_result[setting.key], setting.type)
734 setting_name, form_result[setting.key], setting.type)
704 session.add(sett)
735 session.add(sett)
705
736
706 except Exception:
737 except Exception:
707 log.exception('Exception while updating lab settings')
738 log.exception('Exception while updating lab settings')
708 h.flash(_('Error occurred during updating labs settings'),
739 h.flash(_('Error occurred during updating labs settings'),
709 category='error')
740 category='error')
710 else:
741 else:
711 Session().commit()
742 Session().commit()
712 SettingsModel().invalidate_settings_cache()
743 SettingsModel().invalidate_settings_cache()
713 h.flash(_('Updated Labs settings'), category='success')
744 h.flash(_('Updated Labs settings'), category='success')
714 return redirect(url('admin_settings_labs'))
745 return redirect(url('admin_settings_labs'))
715
746
716 return htmlfill.render(
747 return htmlfill.render(
717 render('admin/settings/settings.html'),
748 render('admin/settings/settings.html'),
718 defaults=self._form_defaults(),
749 defaults=self._form_defaults(),
719 encoding='UTF-8',
750 encoding='UTF-8',
720 force_defaults=False)
751 force_defaults=False)
721
752
722 @HasPermissionAllDecorator('hg.admin')
753 @HasPermissionAllDecorator('hg.admin')
723 def settings_labs(self):
754 def settings_labs(self):
724 """GET /admin/settings/labs: All items in the collection"""
755 """GET /admin/settings/labs: All items in the collection"""
725 # url('admin_settings_labs')
756 # url('admin_settings_labs')
726 if not c.labs_active:
757 if not c.labs_active:
727 redirect(url('admin_settings'))
758 redirect(url('admin_settings'))
728
759
729 c.active = 'labs'
760 c.active = 'labs'
730 c.lab_settings = _LAB_SETTINGS
761 c.lab_settings = _LAB_SETTINGS
731
762
732 return htmlfill.render(
763 return htmlfill.render(
733 render('admin/settings/settings.html'),
764 render('admin/settings/settings.html'),
734 defaults=self._form_defaults(),
765 defaults=self._form_defaults(),
735 encoding='UTF-8',
766 encoding='UTF-8',
736 force_defaults=False)
767 force_defaults=False)
737
768
738 def _form_defaults(self):
769 def _form_defaults(self):
739 defaults = SettingsModel().get_all_settings()
770 defaults = SettingsModel().get_all_settings()
740 defaults.update(self._get_hg_ui_settings())
771 defaults.update(self._get_hg_ui_settings())
741 defaults.update({
772 defaults.update({
742 'new_svn_branch': '',
773 'new_svn_branch': '',
743 'new_svn_tag': '',
774 'new_svn_tag': '',
744 })
775 })
745 return defaults
776 return defaults
746
777
747
778
748 # :param key: name of the setting including the 'rhodecode_' prefix
779 # :param key: name of the setting including the 'rhodecode_' prefix
749 # :param type: the RhodeCodeSetting type to use.
780 # :param type: the RhodeCodeSetting type to use.
750 # :param group: the i18ned group in which we should dispaly this setting
781 # :param group: the i18ned group in which we should dispaly this setting
751 # :param label: the i18ned label we should display for this setting
782 # :param label: the i18ned label we should display for this setting
752 # :param help: the i18ned help we should dispaly for this setting
783 # :param help: the i18ned help we should dispaly for this setting
753 LabSetting = collections.namedtuple(
784 LabSetting = collections.namedtuple(
754 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
785 'LabSetting', ('key', 'type', 'group', 'label', 'help'))
755
786
756
787
757 # This list has to be kept in sync with the form
788 # This list has to be kept in sync with the form
758 # rhodecode.model.forms.LabsSettingsForm.
789 # rhodecode.model.forms.LabsSettingsForm.
759 _LAB_SETTINGS = [
790 _LAB_SETTINGS = [
760 LabSetting(
791 LabSetting(
761 key='rhodecode_hg_use_rebase_for_merging',
792 key='rhodecode_hg_use_rebase_for_merging',
762 type='bool',
793 type='bool',
763 group=lazy_ugettext('Mercurial server-side merge'),
794 group=lazy_ugettext('Mercurial server-side merge'),
764 label=lazy_ugettext('Use rebase instead of creating a merge commit when merging via web interface'),
795 label=lazy_ugettext('Use rebase instead of creating a merge commit when merging via web interface'),
765 help='' # Do not translate the empty string!
796 help='' # Do not translate the empty string!
766 ),
797 ),
767 LabSetting(
798 LabSetting(
768 key='rhodecode_proxy_subversion_http_requests',
799 key='rhodecode_proxy_subversion_http_requests',
769 type='bool',
800 type='bool',
770 group=lazy_ugettext('Subversion HTTP Support'),
801 group=lazy_ugettext('Subversion HTTP Support'),
771 label=lazy_ugettext('Proxy subversion HTTP requests'),
802 label=lazy_ugettext('Proxy subversion HTTP requests'),
772 help='' # Do not translate the empty string!
803 help='' # Do not translate the empty string!
773 ),
804 ),
774 LabSetting(
805 LabSetting(
775 key='rhodecode_subversion_http_server_url',
806 key='rhodecode_subversion_http_server_url',
776 type='str',
807 type='str',
777 group=lazy_ugettext('Subversion HTTP Server URL'),
808 group=lazy_ugettext('Subversion HTTP Server URL'),
778 label='', # Do not translate the empty string!
809 label='', # Do not translate the empty string!
779 help=lazy_ugettext('e.g. http://localhost:8080/')
810 help=lazy_ugettext('e.g. http://localhost:8080/')
780 ),
811 ),
781 ]
812 ]
782
813
783
814
784 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
815 NavListEntry = collections.namedtuple('NavListEntry', ['key', 'name', 'url'])
785
816
786
817
787 class NavEntry(object):
818 class NavEntry(object):
788
819
789 def __init__(self, key, name, view_name, pyramid=False):
820 def __init__(self, key, name, view_name, pyramid=False):
790 self.key = key
821 self.key = key
791 self.name = name
822 self.name = name
792 self.view_name = view_name
823 self.view_name = view_name
793 self.pyramid = pyramid
824 self.pyramid = pyramid
794
825
795 def generate_url(self, request):
826 def generate_url(self, request):
796 if self.pyramid:
827 if self.pyramid:
797 if hasattr(request, 'route_path'):
828 if hasattr(request, 'route_path'):
798 return request.route_path(self.view_name)
829 return request.route_path(self.view_name)
799 else:
830 else:
800 # TODO: johbo: Remove this after migrating to pyramid.
831 # TODO: johbo: Remove this after migrating to pyramid.
801 # We need the pyramid request here to generate URLs to pyramid
832 # We need the pyramid request here to generate URLs to pyramid
802 # views from within pylons views.
833 # views from within pylons views.
803 from pyramid.threadlocal import get_current_request
834 from pyramid.threadlocal import get_current_request
804 pyramid_request = get_current_request()
835 pyramid_request = get_current_request()
805 return pyramid_request.route_path(self.view_name)
836 return pyramid_request.route_path(self.view_name)
806 else:
837 else:
807 return url(self.view_name)
838 return url(self.view_name)
808
839
809
840
810 class NavigationRegistry(object):
841 class NavigationRegistry(object):
811
842
812 _base_entries = [
843 _base_entries = [
813 NavEntry('global', lazy_ugettext('Global'), 'admin_settings_global'),
844 NavEntry('global', lazy_ugettext('Global'), 'admin_settings_global'),
814 NavEntry('vcs', lazy_ugettext('VCS'), 'admin_settings_vcs'),
845 NavEntry('vcs', lazy_ugettext('VCS'), 'admin_settings_vcs'),
815 NavEntry('visual', lazy_ugettext('Visual'), 'admin_settings_visual'),
846 NavEntry('visual', lazy_ugettext('Visual'), 'admin_settings_visual'),
816 NavEntry('mapping', lazy_ugettext('Remap and Rescan'),
847 NavEntry('mapping', lazy_ugettext('Remap and Rescan'),
817 'admin_settings_mapping'),
848 'admin_settings_mapping'),
818 NavEntry('issuetracker', lazy_ugettext('Issue Tracker'),
849 NavEntry('issuetracker', lazy_ugettext('Issue Tracker'),
819 'admin_settings_issuetracker'),
850 'admin_settings_issuetracker'),
820 NavEntry('email', lazy_ugettext('Email'), 'admin_settings_email'),
851 NavEntry('email', lazy_ugettext('Email'), 'admin_settings_email'),
821 NavEntry('hooks', lazy_ugettext('Hooks'), 'admin_settings_hooks'),
852 NavEntry('hooks', lazy_ugettext('Hooks'), 'admin_settings_hooks'),
822 NavEntry('search', lazy_ugettext('Full Text Search'),
853 NavEntry('search', lazy_ugettext('Full Text Search'),
823 'admin_settings_search'),
854 'admin_settings_search'),
824 NavEntry('system', lazy_ugettext('System Info'),
855 NavEntry('system', lazy_ugettext('System Info'),
825 'admin_settings_system'),
856 'admin_settings_system'),
826 NavEntry('open_source', lazy_ugettext('Open Source Licenses'),
857 NavEntry('open_source', lazy_ugettext('Open Source Licenses'),
827 'admin_settings_open_source', pyramid=True),
858 'admin_settings_open_source', pyramid=True),
828 # TODO: marcink: we disable supervisor now until the supervisor stats
859 # TODO: marcink: we disable supervisor now until the supervisor stats
829 # page is fixed in the nix configuration
860 # page is fixed in the nix configuration
830 # NavEntry('supervisor', lazy_ugettext('Supervisor'),
861 # NavEntry('supervisor', lazy_ugettext('Supervisor'),
831 # 'admin_settings_supervisor'),
862 # 'admin_settings_supervisor'),
832 ]
863 ]
833
864
834 def __init__(self):
865 def __init__(self):
835 self._registered_entries = collections.OrderedDict([
866 self._registered_entries = collections.OrderedDict([
836 (item.key, item) for item in self.__class__._base_entries
867 (item.key, item) for item in self.__class__._base_entries
837 ])
868 ])
838
869
839 # Add the labs entry when it's activated.
870 # Add the labs entry when it's activated.
840 labs_active = str2bool(
871 labs_active = str2bool(
841 rhodecode.CONFIG.get('labs_settings_active', 'false'))
872 rhodecode.CONFIG.get('labs_settings_active', 'false'))
842 if labs_active:
873 if labs_active:
843 self.add_entry(
874 self.add_entry(
844 NavEntry('labs', lazy_ugettext('Labs'), 'admin_settings_labs'))
875 NavEntry('labs', lazy_ugettext('Labs'), 'admin_settings_labs'))
845
876
846 def add_entry(self, entry):
877 def add_entry(self, entry):
847 self._registered_entries[entry.key] = entry
878 self._registered_entries[entry.key] = entry
848
879
849 def get_navlist(self, request):
880 def get_navlist(self, request):
850 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
881 navlist = [NavListEntry(i.key, i.name, i.generate_url(request))
851 for i in self._registered_entries.values()]
882 for i in self._registered_entries.values()]
852 return navlist
883 return navlist
853
884
854 navigation = NavigationRegistry()
885 navigation = NavigationRegistry()
@@ -1,86 +1,89 b''
1 <%
1 <%
2 elems = [
2 elems = [
3 ## general
3 ## general
4 (_('RhodeCode Enterprise version'), h.literal('%s <div class="link" id="check_for_update" >%s</div>' % (c.rhodecode_version, _('check for updates'))), ''),
4 (_('RhodeCode Enterprise version'), h.literal('%s <div class="link" id="check_for_update" >%s</div>' % (c.rhodecode_version, _('check for updates'))), ''),
5 (_('Upgrade info endpoint'), h.literal('%s <br/><span >%s.</span>' % (c.rhodecode_update_url, _('Note: please make sure this server can access this url'))), ''),
5 (_('Upgrade info endpoint'), h.literal('%s <br/><span >%s.</span>' % (c.rhodecode_update_url, _('Note: please make sure this server can access this url'))), ''),
6 (_('Configuration INI file'), c.rhodecode_config_ini, ''),
6 (_('Configuration INI file'), c.rhodecode_config_ini, ''),
7 ## systems stats
7 ## systems stats
8 (_('RhodeCode Enterprise Server IP'), c.server_ip, ''),
8 (_('RhodeCode Enterprise Server IP'), c.server_ip, ''),
9 (_('RhodeCode Enterprise Server ID'), c.server_id, ''),
9 (_('RhodeCode Enterprise Server ID'), c.server_id, ''),
10 (_('Platform'), c.platform, ''),
10 (_('Platform'), c.platform, ''),
11 (_('Uptime'), c.uptime_age, ''),
11 (_('Uptime'), c.uptime_age, ''),
12 (_('Storage location'), c.storage, ''),
12 (_('Storage location'), c.storage, ''),
13 (_('Storage disk space'), "%s/%s, %s%% used%s" % (h.format_byte_size_binary(c.disk['used']), h.format_byte_size_binary(c.disk['total']),(c.disk['percent']), ' %s' % c.disk['error'] if 'error' in c.disk else ''), ''),
13 (_('Storage disk space'), "%s/%s, %s%% used%s" % (h.format_byte_size_binary(c.disk['used']), h.format_byte_size_binary(c.disk['total']),(c.disk['percent']), ' %s' % c.disk['error'] if 'error' in c.disk else ''), ''),
14
14
15 (_('Search index storage'), c.index_storage, ''),
15 (_('Search index storage'), c.index_storage, ''),
16 (_('Search index size'), "%s %s" % (h.format_byte_size_binary(c.disk_index['used']), ' %s' % c.disk_index['error'] if 'error' in c.disk_index else ''), ''),
16 (_('Search index size'), "%s %s" % (h.format_byte_size_binary(c.disk_index['used']), ' %s' % c.disk_index['error'] if 'error' in c.disk_index else ''), ''),
17
17
18 (_('Gist storage'), c.gist_storage, ''),
18 (_('Gist storage'), c.gist_storage, ''),
19 (_('Gist storage size'), "%s (%s items)%s" % (h.format_byte_size_binary(c.disk_gist['used']),c.disk_gist['items'], ' %s' % c.disk_gist['error'] if 'error' in c.disk_gist else ''), ''),
19 (_('Gist storage size'), "%s (%s items)%s" % (h.format_byte_size_binary(c.disk_gist['used']),c.disk_gist['items'], ' %s' % c.disk_gist['error'] if 'error' in c.disk_gist else ''), ''),
20
20
21 (_('Archive cache'), h.literal('%s <br/><span >%s.</span>' % (c.archive_storage, _('Enable this by setting archive_cache_dir=/path/to/cache option in the .ini file'))), ''),
21 (_('Archive cache'), h.literal('%s <br/><span >%s.</span>' % (c.archive_storage, _('Enable this by setting archive_cache_dir=/path/to/cache option in the .ini file'))), ''),
22 (_('Archive cache size'), "%s%s" % (h.format_byte_size_binary(c.disk_archive['used']), ' %s' % c.disk_archive['error'] if 'error' in c.disk_archive else ''), ''),
22 (_('Archive cache size'), "%s%s" % (h.format_byte_size_binary(c.disk_archive['used']), ' %s' % c.disk_archive['error'] if 'error' in c.disk_archive else ''), ''),
23
23
24 (_('System memory'), c.system_memory, ''),
24 (_('System memory'), c.system_memory, ''),
25 (_('CPU'), '%s %%' %(c.cpu), ''),
25 (_('CPU'), '%s %%' %(c.cpu), ''),
26 (_('Load'), '1min: %s, 5min: %s, 15min: %s' %(c.load['1_min'],c.load['5_min'],c.load['15_min']), ''),
26 (_('Load'), '1min: %s, 5min: %s, 15min: %s' %(c.load['1_min'],c.load['5_min'],c.load['15_min']), ''),
27
27
28 ## rhodecode stuff
28 ## rhodecode stuff
29 (_('Python version'), c.py_version, ''),
29 (_('Python version'), c.py_version, ''),
30 (_('Python path'), c.py_path, ''),
30 (_('Python path'), c.py_path, ''),
31 (_('GIT version'), c.git_version, ''),
31 (_('GIT version'), c.git_version, ''),
32 (_('HG version'), c.hg_version, ''),
32 (_('HG version'), c.hg_version, ''),
33 (_('SVN version'), c.svn_version, ''),
33 (_('SVN version'), c.svn_version, ''),
34 (_('Database'), "%s @ version: %s" % (c.db_type, c.db_migrate_version), ''),
34 (_('Database'), "%s @ version: %s" % (c.db_type, c.db_migrate_version), ''),
35 (_('Database version'), c.db_version, ''),
35 (_('Database version'), c.db_version, ''),
36
36
37 ]
37 ]
38 %>
38 %>
39
39
40 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
40 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
41 <div>${_('Checking for updates...')}</div>
41 <div>${_('Checking for updates...')}</div>
42 </div>
42 </div>
43
43
44
44
45 <div class="panel panel-default">
45 <div class="panel panel-default">
46 <div class="panel-heading">
46 <div class="panel-heading">
47 <h3 class="panel-title">${_('System Info')}</h3>
47 <h3 class="panel-title">${_('System Info')}</h3>
48 % if c.allowed_to_snapshot:
49 <a href="${url('admin_settings_system', snapshot=1)}" class="panel-edit">${_('create snapshot')}</a>
50 % endif
48 </div>
51 </div>
49 <div class="panel-body">
52 <div class="panel-body">
50 <dl class="dl-horizontal settings">
53 <dl class="dl-horizontal settings">
51 %for dt, dd, tt in elems:
54 %for dt, dd, tt in elems:
52 <dt >${dt}:</dt>
55 <dt>${dt}:</dt>
53 <dd title="${tt}">${dd}</dd>
56 <dd title="${tt}">${dd}</dd>
54 %endfor
57 %endfor
55 </dl>
58 </dl>
56 </div>
59 </div>
57 </div>
60 </div>
58
61
59 <div class="panel panel-default">
62 <div class="panel panel-default">
60 <div class="panel-heading">
63 <div class="panel-heading">
61 <h3 class="panel-title">${_('Python Packages')}</h3>
64 <h3 class="panel-title">${_('Python Packages')}</h3>
62 </div>
65 </div>
63 <div class="panel-body">
66 <div class="panel-body">
64 <table class="table">
67 <table class="table">
65 <colgroup>
68 <colgroup>
66 <col class='label'>
69 <col class='label'>
67 <col class='content'>
70 <col class='content'>
68 </colgroup>
71 </colgroup>
69 <tbody>
72 <tbody>
70 %for key, value in c.py_modules:
73 %for key, value in c.py_modules:
71 <tr>
74 <tr>
72 <td >${key}</td>
75 <td>${key}</td>
73 <td>${value}</td>
76 <td>${value}</td>
74 </tr>
77 </tr>
75 %endfor
78 %endfor
76 </tbody>
79 </tbody>
77 </table>
80 </table>
78 </div>
81 </div>
79 </div>
82 </div>
80
83
81 <script>
84 <script>
82 $('#check_for_update').click(function(e){
85 $('#check_for_update').click(function(e){
83 $('#update_notice').show();
86 $('#update_notice').show();
84 $('#update_notice').load("${h.url('admin_settings_system_update')}");
87 $('#update_notice').load("${h.url('admin_settings_system_update')}");
85 })
88 })
86 </script>
89 </script>
General Comments 0
You need to be logged in to leave comments. Login now