##// END OF EJS Templates
system-info: expose data about vcsserver.
dan -
r3943:2703bca5 default
parent child Browse files
Show More
@@ -1,202 +1,206 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 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 logging
21 import logging
22 import urllib2
22 import urllib2
23
23
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 import rhodecode
26 import rhodecode
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.apps._base.navigation import navigation_list
28 from rhodecode.apps._base.navigation import navigation_list
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
31 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib import system_info
32 from rhodecode.lib import system_info
33 from rhodecode.model.update import UpdateModel
33 from rhodecode.model.update import UpdateModel
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 class AdminSystemInfoSettingsView(BaseAppView):
38 class AdminSystemInfoSettingsView(BaseAppView):
39 def load_default_context(self):
39 def load_default_context(self):
40 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
41 return c
41 return c
42
42
43 @LoginRequired()
43 @LoginRequired()
44 @HasPermissionAllDecorator('hg.admin')
44 @HasPermissionAllDecorator('hg.admin')
45 @view_config(
45 @view_config(
46 route_name='admin_settings_system', request_method='GET',
46 route_name='admin_settings_system', request_method='GET',
47 renderer='rhodecode:templates/admin/settings/settings.mako')
47 renderer='rhodecode:templates/admin/settings/settings.mako')
48 def settings_system_info(self):
48 def settings_system_info(self):
49 _ = self.request.translate
49 _ = self.request.translate
50 c = self.load_default_context()
50 c = self.load_default_context()
51
51
52 c.active = 'system'
52 c.active = 'system'
53 c.navlist = navigation_list(self.request)
53 c.navlist = navigation_list(self.request)
54
54
55 # TODO(marcink), figure out how to allow only selected users to do this
55 # TODO(marcink), figure out how to allow only selected users to do this
56 c.allowed_to_snapshot = self._rhodecode_user.admin
56 c.allowed_to_snapshot = self._rhodecode_user.admin
57
57
58 snapshot = str2bool(self.request.params.get('snapshot'))
58 snapshot = str2bool(self.request.params.get('snapshot'))
59
59
60 c.rhodecode_update_url = UpdateModel().get_update_url()
60 c.rhodecode_update_url = UpdateModel().get_update_url()
61 server_info = system_info.get_system_info(self.request.environ)
61 server_info = system_info.get_system_info(self.request.environ)
62
62
63 for key, val in server_info.items():
63 for key, val in server_info.items():
64 setattr(c, key, val)
64 setattr(c, key, val)
65
65
66 def val(name, subkey='human_value'):
66 def val(name, subkey='human_value'):
67 return server_info[name][subkey]
67 return server_info[name][subkey]
68
68
69 def state(name):
69 def state(name):
70 return server_info[name]['state']
70 return server_info[name]['state']
71
71
72 def val2(name):
72 def val2(name):
73 val = server_info[name]['human_value']
73 val = server_info[name]['human_value']
74 state = server_info[name]['state']
74 state = server_info[name]['state']
75 return val, state
75 return val, state
76
76
77 update_info_msg = _('Note: please make sure this server can '
77 update_info_msg = _('Note: please make sure this server can '
78 'access `${url}` for the update link to work',
78 'access `${url}` for the update link to work',
79 mapping=dict(url=c.rhodecode_update_url))
79 mapping=dict(url=c.rhodecode_update_url))
80 version = UpdateModel().get_stored_version()
80 version = UpdateModel().get_stored_version()
81 is_outdated = UpdateModel().is_outdated(
81 is_outdated = UpdateModel().is_outdated(
82 rhodecode.__version__, version)
82 rhodecode.__version__, version)
83 update_state = {
83 update_state = {
84 'type': 'warning',
84 'type': 'warning',
85 'message': 'New version available: {}'.format(version)
85 'message': 'New version available: {}'.format(version)
86 } \
86 } \
87 if is_outdated else {}
87 if is_outdated else {}
88 c.data_items = [
88 c.data_items = [
89 # update info
89 # update info
90 (_('Update info'), h.literal(
90 (_('Update info'), h.literal(
91 '<span class="link" id="check_for_update" >%s.</span>' % (
91 '<span class="link" id="check_for_update" >%s.</span>' % (
92 _('Check for updates')) +
92 _('Check for updates')) +
93 '<br/> <span >%s.</span>' % (update_info_msg)
93 '<br/> <span >%s.</span>' % (update_info_msg)
94 ), ''),
94 ), ''),
95
95
96 # RhodeCode specific
96 # RhodeCode specific
97 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
97 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
98 (_('Latest version'), version, update_state),
98 (_('Latest version'), version, update_state),
99 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
99 (_('RhodeCode Base URL'), val('rhodecode_config')['config'].get('app.base_url'), state('rhodecode_config')),
100 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
100 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
101 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
101 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
102 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
102 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
103 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
103 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
104 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
104 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
105 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
105 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
106 ('', '', ''), # spacer
106 ('', '', ''), # spacer
107
107
108 # Database
108 # Database
109 (_('Database'), val('database')['url'], state('database')),
109 (_('Database'), val('database')['url'], state('database')),
110 (_('Database version'), val('database')['version'], state('database')),
110 (_('Database version'), val('database')['version'], state('database')),
111 ('', '', ''), # spacer
111 ('', '', ''), # spacer
112
112
113 # Platform/Python
113 # Platform/Python
114 (_('Platform'), val('platform')['name'], state('platform')),
114 (_('Platform'), val('platform')['name'], state('platform')),
115 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
115 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
116 (_('Lang'), val('locale'), state('locale')),
116 (_('Lang'), val('locale'), state('locale')),
117 (_('Python version'), val('python')['version'], state('python')),
117 (_('Python version'), val('python')['version'], state('python')),
118 (_('Python path'), val('python')['executable'], state('python')),
118 (_('Python path'), val('python')['executable'], state('python')),
119 ('', '', ''), # spacer
119 ('', '', ''), # spacer
120
120
121 # Systems stats
121 # Systems stats
122 (_('CPU'), val('cpu')['text'], state('cpu')),
122 (_('CPU'), val('cpu')['text'], state('cpu')),
123 (_('Load'), val('load')['text'], state('load')),
123 (_('Load'), val('load')['text'], state('load')),
124 (_('Memory'), val('memory')['text'], state('memory')),
124 (_('Memory'), val('memory')['text'], state('memory')),
125 (_('Uptime'), val('uptime')['text'], state('uptime')),
125 (_('Uptime'), val('uptime')['text'], state('uptime')),
126 ('', '', ''), # spacer
126 ('', '', ''), # spacer
127
127
128 # ulimit
128 # ulimit
129 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
129 (_('Ulimit'), val('ulimit')['text'], state('ulimit')),
130
130
131 # Repo storage
131 # Repo storage
132 (_('Storage location'), val('storage')['path'], state('storage')),
132 (_('Storage location'), val('storage')['path'], state('storage')),
133 (_('Storage info'), val('storage')['text'], state('storage')),
133 (_('Storage info'), val('storage')['text'], state('storage')),
134 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
134 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
135
135
136 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
136 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
137 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
137 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
138
138
139 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
139 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
140 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
140 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
141
141
142 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
142 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
143 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
143 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
144
144
145 (_('Search info'), val('search')['text'], state('search')),
145 (_('Search info'), val('search')['text'], state('search')),
146 (_('Search location'), val('search')['location'], state('search')),
146 (_('Search location'), val('search')['location'], state('search')),
147 ('', '', ''), # spacer
147 ('', '', ''), # spacer
148
148
149 # VCS specific
149 # VCS specific
150 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
150 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
151 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
151 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
152 (_('GIT'), val('git'), state('git')),
152 (_('GIT'), val('git'), state('git')),
153 (_('HG'), val('hg'), state('hg')),
153 (_('HG'), val('hg'), state('hg')),
154 (_('SVN'), val('svn'), state('svn')),
154 (_('SVN'), val('svn'), state('svn')),
155
155
156 ]
156 ]
157
157
158 c.vcsserver_data_items = [
159 (k, v) for k,v in (val('vcs_server_config') or {}).items()
160 ]
161
158 if snapshot:
162 if snapshot:
159 if c.allowed_to_snapshot:
163 if c.allowed_to_snapshot:
160 c.data_items.pop(0) # remove server info
164 c.data_items.pop(0) # remove server info
161 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
165 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
162 else:
166 else:
163 h.flash('You are not allowed to do this', category='warning')
167 h.flash('You are not allowed to do this', category='warning')
164 return self._get_template_context(c)
168 return self._get_template_context(c)
165
169
166 @LoginRequired()
170 @LoginRequired()
167 @HasPermissionAllDecorator('hg.admin')
171 @HasPermissionAllDecorator('hg.admin')
168 @view_config(
172 @view_config(
169 route_name='admin_settings_system_update', request_method='GET',
173 route_name='admin_settings_system_update', request_method='GET',
170 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
174 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
171 def settings_system_info_check_update(self):
175 def settings_system_info_check_update(self):
172 _ = self.request.translate
176 _ = self.request.translate
173 c = self.load_default_context()
177 c = self.load_default_context()
174
178
175 update_url = UpdateModel().get_update_url()
179 update_url = UpdateModel().get_update_url()
176
180
177 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
181 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
178 try:
182 try:
179 data = UpdateModel().get_update_data(update_url)
183 data = UpdateModel().get_update_data(update_url)
180 except urllib2.URLError as e:
184 except urllib2.URLError as e:
181 log.exception("Exception contacting upgrade server")
185 log.exception("Exception contacting upgrade server")
182 self.request.override_renderer = 'string'
186 self.request.override_renderer = 'string'
183 return _err('Failed to contact upgrade server: %r' % e)
187 return _err('Failed to contact upgrade server: %r' % e)
184 except ValueError as e:
188 except ValueError as e:
185 log.exception("Bad data sent from update server")
189 log.exception("Bad data sent from update server")
186 self.request.override_renderer = 'string'
190 self.request.override_renderer = 'string'
187 return _err('Bad data sent from update server')
191 return _err('Bad data sent from update server')
188
192
189 latest = data['versions'][0]
193 latest = data['versions'][0]
190
194
191 c.update_url = update_url
195 c.update_url = update_url
192 c.latest_data = latest
196 c.latest_data = latest
193 c.latest_ver = latest['version']
197 c.latest_ver = latest['version']
194 c.cur_ver = rhodecode.__version__
198 c.cur_ver = rhodecode.__version__
195 c.should_upgrade = False
199 c.should_upgrade = False
196
200
197 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
201 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
198 if is_oudated:
202 if is_oudated:
199 c.should_upgrade = True
203 c.should_upgrade = True
200 c.important_notices = latest['general']
204 c.important_notices = latest['general']
201 UpdateModel().store_version(latest['version'])
205 UpdateModel().store_version(latest['version'])
202 return self._get_template_context(c)
206 return self._get_template_context(c)
@@ -1,777 +1,796 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2019 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 os
22 import os
23 import sys
23 import sys
24 import time
24 import time
25 import platform
25 import platform
26 import collections
26 import collections
27 import pkg_resources
27 import pkg_resources
28 import logging
28 import logging
29 import resource
29 import resource
30
30
31 from pyramid.compat import configparser
31 from pyramid.compat import configparser
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 psutil = None
36 psutil = None
37
37
38 try:
38 try:
39 # cygwin cannot have yet psutil support.
39 # cygwin cannot have yet psutil support.
40 import psutil as psutil
40 import psutil as psutil
41 except ImportError:
41 except ImportError:
42 pass
42 pass
43
43
44
44
45 _NA = 'NOT AVAILABLE'
45 _NA = 'NOT AVAILABLE'
46
46
47 STATE_OK = 'ok'
47 STATE_OK = 'ok'
48 STATE_ERR = 'error'
48 STATE_ERR = 'error'
49 STATE_WARN = 'warning'
49 STATE_WARN = 'warning'
50
50
51 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
51 STATE_OK_DEFAULT = {'message': '', 'type': STATE_OK}
52
52
53
53
54 # HELPERS
54 # HELPERS
55 def percentage(part, whole):
55 def percentage(part, whole):
56 whole = float(whole)
56 whole = float(whole)
57 if whole > 0:
57 if whole > 0:
58 return round(100 * float(part) / whole, 1)
58 return round(100 * float(part) / whole, 1)
59 return 0.0
59 return 0.0
60
60
61
61
62 def get_storage_size(storage_path):
62 def get_storage_size(storage_path):
63 sizes = []
63 sizes = []
64 for file_ in os.listdir(storage_path):
64 for file_ in os.listdir(storage_path):
65 storage_file = os.path.join(storage_path, file_)
65 storage_file = os.path.join(storage_path, file_)
66 if os.path.isfile(storage_file):
66 if os.path.isfile(storage_file):
67 try:
67 try:
68 sizes.append(os.path.getsize(storage_file))
68 sizes.append(os.path.getsize(storage_file))
69 except OSError:
69 except OSError:
70 log.exception('Failed to get size of storage file %s', storage_file)
70 log.exception('Failed to get size of storage file %s', storage_file)
71 pass
71 pass
72
72
73 return sum(sizes)
73 return sum(sizes)
74
74
75
75
76 def get_resource(resource_type):
76 def get_resource(resource_type):
77 try:
77 try:
78 return resource.getrlimit(resource_type)
78 return resource.getrlimit(resource_type)
79 except Exception:
79 except Exception:
80 return 'NOT_SUPPORTED'
80 return 'NOT_SUPPORTED'
81
81
82
82
83 def get_cert_path(ini_path):
83 def get_cert_path(ini_path):
84 default = '/etc/ssl/certs/ca-certificates.crt'
84 default = '/etc/ssl/certs/ca-certificates.crt'
85 control_ca_bundle = os.path.join(
85 control_ca_bundle = os.path.join(
86 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(ini_path)))),
86 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(ini_path)))),
87 '.rccontrol-profile/etc/ca-bundle.crt')
87 '.rccontrol-profile/etc/ca-bundle.crt')
88 if os.path.isfile(control_ca_bundle):
88 if os.path.isfile(control_ca_bundle):
89 default = control_ca_bundle
89 default = control_ca_bundle
90
90
91 return default
91 return default
92
92
93
93
94 class SysInfoRes(object):
94 class SysInfoRes(object):
95 def __init__(self, value, state=None, human_value=None):
95 def __init__(self, value, state=None, human_value=None):
96 self.value = value
96 self.value = value
97 self.state = state or STATE_OK_DEFAULT
97 self.state = state or STATE_OK_DEFAULT
98 self.human_value = human_value or value
98 self.human_value = human_value or value
99
99
100 def __json__(self):
100 def __json__(self):
101 return {
101 return {
102 'value': self.value,
102 'value': self.value,
103 'state': self.state,
103 'state': self.state,
104 'human_value': self.human_value,
104 'human_value': self.human_value,
105 }
105 }
106
106
107 def get_value(self):
107 def get_value(self):
108 return self.__json__()
108 return self.__json__()
109
109
110 def __str__(self):
110 def __str__(self):
111 return '<SysInfoRes({})>'.format(self.__json__())
111 return '<SysInfoRes({})>'.format(self.__json__())
112
112
113
113
114 class SysInfo(object):
114 class SysInfo(object):
115
115
116 def __init__(self, func_name, **kwargs):
116 def __init__(self, func_name, **kwargs):
117 self.func_name = func_name
117 self.func_name = func_name
118 self.value = _NA
118 self.value = _NA
119 self.state = None
119 self.state = None
120 self.kwargs = kwargs or {}
120 self.kwargs = kwargs or {}
121
121
122 def __call__(self):
122 def __call__(self):
123 computed = self.compute(**self.kwargs)
123 computed = self.compute(**self.kwargs)
124 if not isinstance(computed, SysInfoRes):
124 if not isinstance(computed, SysInfoRes):
125 raise ValueError(
125 raise ValueError(
126 'computed value for {} is not instance of '
126 'computed value for {} is not instance of '
127 '{}, got {} instead'.format(
127 '{}, got {} instead'.format(
128 self.func_name, SysInfoRes, type(computed)))
128 self.func_name, SysInfoRes, type(computed)))
129 return computed.__json__()
129 return computed.__json__()
130
130
131 def __str__(self):
131 def __str__(self):
132 return '<SysInfo({})>'.format(self.func_name)
132 return '<SysInfo({})>'.format(self.func_name)
133
133
134 def compute(self, **kwargs):
134 def compute(self, **kwargs):
135 return self.func_name(**kwargs)
135 return self.func_name(**kwargs)
136
136
137
137
138 # SysInfo functions
138 # SysInfo functions
139 def python_info():
139 def python_info():
140 value = dict(version=' '.join(platform._sys_version()),
140 value = dict(version=' '.join(platform._sys_version()),
141 executable=sys.executable)
141 executable=sys.executable)
142 return SysInfoRes(value=value)
142 return SysInfoRes(value=value)
143
143
144
144
145 def py_modules():
145 def py_modules():
146 mods = dict([(p.project_name, p.version)
146 mods = dict([(p.project_name, p.version)
147 for p in pkg_resources.working_set])
147 for p in pkg_resources.working_set])
148 value = sorted(mods.items(), key=lambda k: k[0].lower())
148 value = sorted(mods.items(), key=lambda k: k[0].lower())
149 return SysInfoRes(value=value)
149 return SysInfoRes(value=value)
150
150
151
151
152 def platform_type():
152 def platform_type():
153 from rhodecode.lib.utils import safe_unicode, generate_platform_uuid
153 from rhodecode.lib.utils import safe_unicode, generate_platform_uuid
154
154
155 value = dict(
155 value = dict(
156 name=safe_unicode(platform.platform()),
156 name=safe_unicode(platform.platform()),
157 uuid=generate_platform_uuid()
157 uuid=generate_platform_uuid()
158 )
158 )
159 return SysInfoRes(value=value)
159 return SysInfoRes(value=value)
160
160
161
161
162 def locale_info():
162 def locale_info():
163 import locale
163 import locale
164
164
165 value = dict(
165 value = dict(
166 locale_default=locale.getdefaultlocale(),
166 locale_default=locale.getdefaultlocale(),
167 locale_lc_all=locale.getlocale(locale.LC_ALL),
167 locale_lc_all=locale.getlocale(locale.LC_ALL),
168 lang_env=os.environ.get('LANG'),
168 lang_env=os.environ.get('LANG'),
169 lc_all_env=os.environ.get('LC_ALL'),
169 lc_all_env=os.environ.get('LC_ALL'),
170 local_archive_env=os.environ.get('LOCALE_ARCHIVE'),
170 local_archive_env=os.environ.get('LOCALE_ARCHIVE'),
171 )
171 )
172 human_value = 'LANG: {}, locale LC_ALL: {}, Default locales: {}'.format(
172 human_value = 'LANG: {}, locale LC_ALL: {}, Default locales: {}'.format(
173 value['lang_env'], value['locale_lc_all'], value['locale_default'])
173 value['lang_env'], value['locale_lc_all'], value['locale_default'])
174 return SysInfoRes(value=value, human_value=human_value)
174 return SysInfoRes(value=value, human_value=human_value)
175
175
176
176
177 def ulimit_info():
177 def ulimit_info():
178 data = collections.OrderedDict([
178 data = collections.OrderedDict([
179 ('cpu time (seconds)', get_resource(resource.RLIMIT_CPU)),
179 ('cpu time (seconds)', get_resource(resource.RLIMIT_CPU)),
180 ('file size', get_resource(resource.RLIMIT_FSIZE)),
180 ('file size', get_resource(resource.RLIMIT_FSIZE)),
181 ('stack size', get_resource(resource.RLIMIT_STACK)),
181 ('stack size', get_resource(resource.RLIMIT_STACK)),
182 ('core file size', get_resource(resource.RLIMIT_CORE)),
182 ('core file size', get_resource(resource.RLIMIT_CORE)),
183 ('address space size', get_resource(resource.RLIMIT_AS)),
183 ('address space size', get_resource(resource.RLIMIT_AS)),
184 ('locked in mem size', get_resource(resource.RLIMIT_MEMLOCK)),
184 ('locked in mem size', get_resource(resource.RLIMIT_MEMLOCK)),
185 ('heap size', get_resource(resource.RLIMIT_DATA)),
185 ('heap size', get_resource(resource.RLIMIT_DATA)),
186 ('rss size', get_resource(resource.RLIMIT_RSS)),
186 ('rss size', get_resource(resource.RLIMIT_RSS)),
187 ('number of processes', get_resource(resource.RLIMIT_NPROC)),
187 ('number of processes', get_resource(resource.RLIMIT_NPROC)),
188 ('open files', get_resource(resource.RLIMIT_NOFILE)),
188 ('open files', get_resource(resource.RLIMIT_NOFILE)),
189 ])
189 ])
190
190
191 text = ', '.join('{}:{}'.format(k, v) for k, v in data.items())
191 text = ', '.join('{}:{}'.format(k, v) for k, v in data.items())
192
192
193 value = {
193 value = {
194 'limits': data,
194 'limits': data,
195 'text': text,
195 'text': text,
196 }
196 }
197 return SysInfoRes(value=value)
197 return SysInfoRes(value=value)
198
198
199
199
200 def uptime():
200 def uptime():
201 from rhodecode.lib.helpers import age, time_to_datetime
201 from rhodecode.lib.helpers import age, time_to_datetime
202 from rhodecode.translation import TranslationString
202 from rhodecode.translation import TranslationString
203
203
204 value = dict(boot_time=0, uptime=0, text='')
204 value = dict(boot_time=0, uptime=0, text='')
205 state = STATE_OK_DEFAULT
205 state = STATE_OK_DEFAULT
206 if not psutil:
206 if not psutil:
207 return SysInfoRes(value=value, state=state)
207 return SysInfoRes(value=value, state=state)
208
208
209 boot_time = psutil.boot_time()
209 boot_time = psutil.boot_time()
210 value['boot_time'] = boot_time
210 value['boot_time'] = boot_time
211 value['uptime'] = time.time() - boot_time
211 value['uptime'] = time.time() - boot_time
212
212
213 date_or_age = age(time_to_datetime(boot_time))
213 date_or_age = age(time_to_datetime(boot_time))
214 if isinstance(date_or_age, TranslationString):
214 if isinstance(date_or_age, TranslationString):
215 date_or_age = date_or_age.interpolate()
215 date_or_age = date_or_age.interpolate()
216
216
217 human_value = value.copy()
217 human_value = value.copy()
218 human_value['boot_time'] = time_to_datetime(boot_time)
218 human_value['boot_time'] = time_to_datetime(boot_time)
219 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
219 human_value['uptime'] = age(time_to_datetime(boot_time), show_suffix=False)
220
220
221 human_value['text'] = u'Server started {}'.format(date_or_age)
221 human_value['text'] = u'Server started {}'.format(date_or_age)
222 return SysInfoRes(value=value, human_value=human_value)
222 return SysInfoRes(value=value, human_value=human_value)
223
223
224
224
225 def memory():
225 def memory():
226 from rhodecode.lib.helpers import format_byte_size_binary
226 from rhodecode.lib.helpers import format_byte_size_binary
227 value = dict(available=0, used=0, used_real=0, cached=0, percent=0,
227 value = dict(available=0, used=0, used_real=0, cached=0, percent=0,
228 percent_used=0, free=0, inactive=0, active=0, shared=0,
228 percent_used=0, free=0, inactive=0, active=0, shared=0,
229 total=0, buffers=0, text='')
229 total=0, buffers=0, text='')
230
230
231 state = STATE_OK_DEFAULT
231 state = STATE_OK_DEFAULT
232 if not psutil:
232 if not psutil:
233 return SysInfoRes(value=value, state=state)
233 return SysInfoRes(value=value, state=state)
234
234
235 value.update(dict(psutil.virtual_memory()._asdict()))
235 value.update(dict(psutil.virtual_memory()._asdict()))
236 value['used_real'] = value['total'] - value['available']
236 value['used_real'] = value['total'] - value['available']
237 value['percent_used'] = psutil._common.usage_percent(
237 value['percent_used'] = psutil._common.usage_percent(
238 value['used_real'], value['total'], 1)
238 value['used_real'], value['total'], 1)
239
239
240 human_value = value.copy()
240 human_value = value.copy()
241 human_value['text'] = '%s/%s, %s%% used' % (
241 human_value['text'] = '%s/%s, %s%% used' % (
242 format_byte_size_binary(value['used_real']),
242 format_byte_size_binary(value['used_real']),
243 format_byte_size_binary(value['total']),
243 format_byte_size_binary(value['total']),
244 value['percent_used'],)
244 value['percent_used'],)
245
245
246 keys = value.keys()[::]
246 keys = value.keys()[::]
247 keys.pop(keys.index('percent'))
247 keys.pop(keys.index('percent'))
248 keys.pop(keys.index('percent_used'))
248 keys.pop(keys.index('percent_used'))
249 keys.pop(keys.index('text'))
249 keys.pop(keys.index('text'))
250 for k in keys:
250 for k in keys:
251 human_value[k] = format_byte_size_binary(value[k])
251 human_value[k] = format_byte_size_binary(value[k])
252
252
253 if state['type'] == STATE_OK and value['percent_used'] > 90:
253 if state['type'] == STATE_OK and value['percent_used'] > 90:
254 msg = 'Critical: your available RAM memory is very low.'
254 msg = 'Critical: your available RAM memory is very low.'
255 state = {'message': msg, 'type': STATE_ERR}
255 state = {'message': msg, 'type': STATE_ERR}
256
256
257 elif state['type'] == STATE_OK and value['percent_used'] > 70:
257 elif state['type'] == STATE_OK and value['percent_used'] > 70:
258 msg = 'Warning: your available RAM memory is running low.'
258 msg = 'Warning: your available RAM memory is running low.'
259 state = {'message': msg, 'type': STATE_WARN}
259 state = {'message': msg, 'type': STATE_WARN}
260
260
261 return SysInfoRes(value=value, state=state, human_value=human_value)
261 return SysInfoRes(value=value, state=state, human_value=human_value)
262
262
263
263
264 def machine_load():
264 def machine_load():
265 value = {'1_min': _NA, '5_min': _NA, '15_min': _NA, 'text': ''}
265 value = {'1_min': _NA, '5_min': _NA, '15_min': _NA, 'text': ''}
266 state = STATE_OK_DEFAULT
266 state = STATE_OK_DEFAULT
267 if not psutil:
267 if not psutil:
268 return SysInfoRes(value=value, state=state)
268 return SysInfoRes(value=value, state=state)
269
269
270 # load averages
270 # load averages
271 if hasattr(psutil.os, 'getloadavg'):
271 if hasattr(psutil.os, 'getloadavg'):
272 value.update(dict(
272 value.update(dict(
273 zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg())))
273 zip(['1_min', '5_min', '15_min'], psutil.os.getloadavg())))
274
274
275 human_value = value.copy()
275 human_value = value.copy()
276 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
276 human_value['text'] = '1min: {}, 5min: {}, 15min: {}'.format(
277 value['1_min'], value['5_min'], value['15_min'])
277 value['1_min'], value['5_min'], value['15_min'])
278
278
279 if state['type'] == STATE_OK and value['15_min'] > 5:
279 if state['type'] == STATE_OK and value['15_min'] > 5:
280 msg = 'Warning: your machine load is very high.'
280 msg = 'Warning: your machine load is very high.'
281 state = {'message': msg, 'type': STATE_WARN}
281 state = {'message': msg, 'type': STATE_WARN}
282
282
283 return SysInfoRes(value=value, state=state, human_value=human_value)
283 return SysInfoRes(value=value, state=state, human_value=human_value)
284
284
285
285
286 def cpu():
286 def cpu():
287 value = {'cpu': 0, 'cpu_count': 0, 'cpu_usage': []}
287 value = {'cpu': 0, 'cpu_count': 0, 'cpu_usage': []}
288 state = STATE_OK_DEFAULT
288 state = STATE_OK_DEFAULT
289
289
290 if not psutil:
290 if not psutil:
291 return SysInfoRes(value=value, state=state)
291 return SysInfoRes(value=value, state=state)
292
292
293 value['cpu'] = psutil.cpu_percent(0.5)
293 value['cpu'] = psutil.cpu_percent(0.5)
294 value['cpu_usage'] = psutil.cpu_percent(0.5, percpu=True)
294 value['cpu_usage'] = psutil.cpu_percent(0.5, percpu=True)
295 value['cpu_count'] = psutil.cpu_count()
295 value['cpu_count'] = psutil.cpu_count()
296
296
297 human_value = value.copy()
297 human_value = value.copy()
298 human_value['text'] = '{} cores at {} %'.format(
298 human_value['text'] = '{} cores at {} %'.format(
299 value['cpu_count'], value['cpu'])
299 value['cpu_count'], value['cpu'])
300
300
301 return SysInfoRes(value=value, state=state, human_value=human_value)
301 return SysInfoRes(value=value, state=state, human_value=human_value)
302
302
303
303
304 def storage():
304 def storage():
305 from rhodecode.lib.helpers import format_byte_size_binary
305 from rhodecode.lib.helpers import format_byte_size_binary
306 from rhodecode.model.settings import VcsSettingsModel
306 from rhodecode.model.settings import VcsSettingsModel
307 path = VcsSettingsModel().get_repos_location()
307 path = VcsSettingsModel().get_repos_location()
308
308
309 value = dict(percent=0, used=0, total=0, path=path, text='')
309 value = dict(percent=0, used=0, total=0, path=path, text='')
310 state = STATE_OK_DEFAULT
310 state = STATE_OK_DEFAULT
311 if not psutil:
311 if not psutil:
312 return SysInfoRes(value=value, state=state)
312 return SysInfoRes(value=value, state=state)
313
313
314 try:
314 try:
315 value.update(dict(psutil.disk_usage(path)._asdict()))
315 value.update(dict(psutil.disk_usage(path)._asdict()))
316 except Exception as e:
316 except Exception as e:
317 log.exception('Failed to fetch disk info')
317 log.exception('Failed to fetch disk info')
318 state = {'message': str(e), 'type': STATE_ERR}
318 state = {'message': str(e), 'type': STATE_ERR}
319
319
320 human_value = value.copy()
320 human_value = value.copy()
321 human_value['used'] = format_byte_size_binary(value['used'])
321 human_value['used'] = format_byte_size_binary(value['used'])
322 human_value['total'] = format_byte_size_binary(value['total'])
322 human_value['total'] = format_byte_size_binary(value['total'])
323 human_value['text'] = "{}/{}, {}% used".format(
323 human_value['text'] = "{}/{}, {}% used".format(
324 format_byte_size_binary(value['used']),
324 format_byte_size_binary(value['used']),
325 format_byte_size_binary(value['total']),
325 format_byte_size_binary(value['total']),
326 value['percent'])
326 value['percent'])
327
327
328 if state['type'] == STATE_OK and value['percent'] > 90:
328 if state['type'] == STATE_OK and value['percent'] > 90:
329 msg = 'Critical: your disk space is very low.'
329 msg = 'Critical: your disk space is very low.'
330 state = {'message': msg, 'type': STATE_ERR}
330 state = {'message': msg, 'type': STATE_ERR}
331
331
332 elif state['type'] == STATE_OK and value['percent'] > 70:
332 elif state['type'] == STATE_OK and value['percent'] > 70:
333 msg = 'Warning: your disk space is running low.'
333 msg = 'Warning: your disk space is running low.'
334 state = {'message': msg, 'type': STATE_WARN}
334 state = {'message': msg, 'type': STATE_WARN}
335
335
336 return SysInfoRes(value=value, state=state, human_value=human_value)
336 return SysInfoRes(value=value, state=state, human_value=human_value)
337
337
338
338
339 def storage_inodes():
339 def storage_inodes():
340 from rhodecode.model.settings import VcsSettingsModel
340 from rhodecode.model.settings import VcsSettingsModel
341 path = VcsSettingsModel().get_repos_location()
341 path = VcsSettingsModel().get_repos_location()
342
342
343 value = dict(percent=0, free=0, used=0, total=0, path=path, text='')
343 value = dict(percent=0, free=0, used=0, total=0, path=path, text='')
344 state = STATE_OK_DEFAULT
344 state = STATE_OK_DEFAULT
345 if not psutil:
345 if not psutil:
346 return SysInfoRes(value=value, state=state)
346 return SysInfoRes(value=value, state=state)
347
347
348 try:
348 try:
349 i_stat = os.statvfs(path)
349 i_stat = os.statvfs(path)
350 value['free'] = i_stat.f_ffree
350 value['free'] = i_stat.f_ffree
351 value['used'] = i_stat.f_files-i_stat.f_favail
351 value['used'] = i_stat.f_files-i_stat.f_favail
352 value['total'] = i_stat.f_files
352 value['total'] = i_stat.f_files
353 value['percent'] = percentage(value['used'], value['total'])
353 value['percent'] = percentage(value['used'], value['total'])
354 except Exception as e:
354 except Exception as e:
355 log.exception('Failed to fetch disk inodes info')
355 log.exception('Failed to fetch disk inodes info')
356 state = {'message': str(e), 'type': STATE_ERR}
356 state = {'message': str(e), 'type': STATE_ERR}
357
357
358 human_value = value.copy()
358 human_value = value.copy()
359 human_value['text'] = "{}/{}, {}% used".format(
359 human_value['text'] = "{}/{}, {}% used".format(
360 value['used'], value['total'], value['percent'])
360 value['used'], value['total'], value['percent'])
361
361
362 if state['type'] == STATE_OK and value['percent'] > 90:
362 if state['type'] == STATE_OK and value['percent'] > 90:
363 msg = 'Critical: your disk free inodes are very low.'
363 msg = 'Critical: your disk free inodes are very low.'
364 state = {'message': msg, 'type': STATE_ERR}
364 state = {'message': msg, 'type': STATE_ERR}
365
365
366 elif state['type'] == STATE_OK and value['percent'] > 70:
366 elif state['type'] == STATE_OK and value['percent'] > 70:
367 msg = 'Warning: your disk free inodes are running low.'
367 msg = 'Warning: your disk free inodes are running low.'
368 state = {'message': msg, 'type': STATE_WARN}
368 state = {'message': msg, 'type': STATE_WARN}
369
369
370 return SysInfoRes(value=value, state=state, human_value=human_value)
370 return SysInfoRes(value=value, state=state, human_value=human_value)
371
371
372
372
373 def storage_archives():
373 def storage_archives():
374 import rhodecode
374 import rhodecode
375 from rhodecode.lib.utils import safe_str
375 from rhodecode.lib.utils import safe_str
376 from rhodecode.lib.helpers import format_byte_size_binary
376 from rhodecode.lib.helpers import format_byte_size_binary
377
377
378 msg = 'Enable this by setting ' \
378 msg = 'Enable this by setting ' \
379 'archive_cache_dir=/path/to/cache option in the .ini file'
379 'archive_cache_dir=/path/to/cache option in the .ini file'
380 path = safe_str(rhodecode.CONFIG.get('archive_cache_dir', msg))
380 path = safe_str(rhodecode.CONFIG.get('archive_cache_dir', msg))
381
381
382 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
382 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
383 state = STATE_OK_DEFAULT
383 state = STATE_OK_DEFAULT
384 try:
384 try:
385 items_count = 0
385 items_count = 0
386 used = 0
386 used = 0
387 for root, dirs, files in os.walk(path):
387 for root, dirs, files in os.walk(path):
388 if root == path:
388 if root == path:
389 items_count = len(files)
389 items_count = len(files)
390
390
391 for f in files:
391 for f in files:
392 try:
392 try:
393 used += os.path.getsize(os.path.join(root, f))
393 used += os.path.getsize(os.path.join(root, f))
394 except OSError:
394 except OSError:
395 pass
395 pass
396 value.update({
396 value.update({
397 'percent': 100,
397 'percent': 100,
398 'used': used,
398 'used': used,
399 'total': used,
399 'total': used,
400 'items': items_count
400 'items': items_count
401 })
401 })
402
402
403 except Exception as e:
403 except Exception as e:
404 log.exception('failed to fetch archive cache storage')
404 log.exception('failed to fetch archive cache storage')
405 state = {'message': str(e), 'type': STATE_ERR}
405 state = {'message': str(e), 'type': STATE_ERR}
406
406
407 human_value = value.copy()
407 human_value = value.copy()
408 human_value['used'] = format_byte_size_binary(value['used'])
408 human_value['used'] = format_byte_size_binary(value['used'])
409 human_value['total'] = format_byte_size_binary(value['total'])
409 human_value['total'] = format_byte_size_binary(value['total'])
410 human_value['text'] = "{} ({} items)".format(
410 human_value['text'] = "{} ({} items)".format(
411 human_value['used'], value['items'])
411 human_value['used'], value['items'])
412
412
413 return SysInfoRes(value=value, state=state, human_value=human_value)
413 return SysInfoRes(value=value, state=state, human_value=human_value)
414
414
415
415
416 def storage_gist():
416 def storage_gist():
417 from rhodecode.model.gist import GIST_STORE_LOC
417 from rhodecode.model.gist import GIST_STORE_LOC
418 from rhodecode.model.settings import VcsSettingsModel
418 from rhodecode.model.settings import VcsSettingsModel
419 from rhodecode.lib.utils import safe_str
419 from rhodecode.lib.utils import safe_str
420 from rhodecode.lib.helpers import format_byte_size_binary
420 from rhodecode.lib.helpers import format_byte_size_binary
421 path = safe_str(os.path.join(
421 path = safe_str(os.path.join(
422 VcsSettingsModel().get_repos_location(), GIST_STORE_LOC))
422 VcsSettingsModel().get_repos_location(), GIST_STORE_LOC))
423
423
424 # gist storage
424 # gist storage
425 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
425 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
426 state = STATE_OK_DEFAULT
426 state = STATE_OK_DEFAULT
427
427
428 try:
428 try:
429 items_count = 0
429 items_count = 0
430 used = 0
430 used = 0
431 for root, dirs, files in os.walk(path):
431 for root, dirs, files in os.walk(path):
432 if root == path:
432 if root == path:
433 items_count = len(dirs)
433 items_count = len(dirs)
434
434
435 for f in files:
435 for f in files:
436 try:
436 try:
437 used += os.path.getsize(os.path.join(root, f))
437 used += os.path.getsize(os.path.join(root, f))
438 except OSError:
438 except OSError:
439 pass
439 pass
440 value.update({
440 value.update({
441 'percent': 100,
441 'percent': 100,
442 'used': used,
442 'used': used,
443 'total': used,
443 'total': used,
444 'items': items_count
444 'items': items_count
445 })
445 })
446 except Exception as e:
446 except Exception as e:
447 log.exception('failed to fetch gist storage items')
447 log.exception('failed to fetch gist storage items')
448 state = {'message': str(e), 'type': STATE_ERR}
448 state = {'message': str(e), 'type': STATE_ERR}
449
449
450 human_value = value.copy()
450 human_value = value.copy()
451 human_value['used'] = format_byte_size_binary(value['used'])
451 human_value['used'] = format_byte_size_binary(value['used'])
452 human_value['total'] = format_byte_size_binary(value['total'])
452 human_value['total'] = format_byte_size_binary(value['total'])
453 human_value['text'] = "{} ({} items)".format(
453 human_value['text'] = "{} ({} items)".format(
454 human_value['used'], value['items'])
454 human_value['used'], value['items'])
455
455
456 return SysInfoRes(value=value, state=state, human_value=human_value)
456 return SysInfoRes(value=value, state=state, human_value=human_value)
457
457
458
458
459 def storage_temp():
459 def storage_temp():
460 import tempfile
460 import tempfile
461 from rhodecode.lib.helpers import format_byte_size_binary
461 from rhodecode.lib.helpers import format_byte_size_binary
462
462
463 path = tempfile.gettempdir()
463 path = tempfile.gettempdir()
464 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
464 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
465 state = STATE_OK_DEFAULT
465 state = STATE_OK_DEFAULT
466
466
467 if not psutil:
467 if not psutil:
468 return SysInfoRes(value=value, state=state)
468 return SysInfoRes(value=value, state=state)
469
469
470 try:
470 try:
471 value.update(dict(psutil.disk_usage(path)._asdict()))
471 value.update(dict(psutil.disk_usage(path)._asdict()))
472 except Exception as e:
472 except Exception as e:
473 log.exception('Failed to fetch temp dir info')
473 log.exception('Failed to fetch temp dir info')
474 state = {'message': str(e), 'type': STATE_ERR}
474 state = {'message': str(e), 'type': STATE_ERR}
475
475
476 human_value = value.copy()
476 human_value = value.copy()
477 human_value['used'] = format_byte_size_binary(value['used'])
477 human_value['used'] = format_byte_size_binary(value['used'])
478 human_value['total'] = format_byte_size_binary(value['total'])
478 human_value['total'] = format_byte_size_binary(value['total'])
479 human_value['text'] = "{}/{}, {}% used".format(
479 human_value['text'] = "{}/{}, {}% used".format(
480 format_byte_size_binary(value['used']),
480 format_byte_size_binary(value['used']),
481 format_byte_size_binary(value['total']),
481 format_byte_size_binary(value['total']),
482 value['percent'])
482 value['percent'])
483
483
484 return SysInfoRes(value=value, state=state, human_value=human_value)
484 return SysInfoRes(value=value, state=state, human_value=human_value)
485
485
486
486
487 def search_info():
487 def search_info():
488 import rhodecode
488 import rhodecode
489 from rhodecode.lib.index import searcher_from_config
489 from rhodecode.lib.index import searcher_from_config
490
490
491 backend = rhodecode.CONFIG.get('search.module', '')
491 backend = rhodecode.CONFIG.get('search.module', '')
492 location = rhodecode.CONFIG.get('search.location', '')
492 location = rhodecode.CONFIG.get('search.location', '')
493
493
494 try:
494 try:
495 searcher = searcher_from_config(rhodecode.CONFIG)
495 searcher = searcher_from_config(rhodecode.CONFIG)
496 searcher = searcher.__class__.__name__
496 searcher = searcher.__class__.__name__
497 except Exception:
497 except Exception:
498 searcher = None
498 searcher = None
499
499
500 value = dict(
500 value = dict(
501 backend=backend, searcher=searcher, location=location, text='')
501 backend=backend, searcher=searcher, location=location, text='')
502 state = STATE_OK_DEFAULT
502 state = STATE_OK_DEFAULT
503
503
504 human_value = value.copy()
504 human_value = value.copy()
505 human_value['text'] = "backend:`{}`".format(human_value['backend'])
505 human_value['text'] = "backend:`{}`".format(human_value['backend'])
506
506
507 return SysInfoRes(value=value, state=state, human_value=human_value)
507 return SysInfoRes(value=value, state=state, human_value=human_value)
508
508
509
509
510 def git_info():
510 def git_info():
511 from rhodecode.lib.vcs.backends import git
511 from rhodecode.lib.vcs.backends import git
512 state = STATE_OK_DEFAULT
512 state = STATE_OK_DEFAULT
513 value = human_value = ''
513 value = human_value = ''
514 try:
514 try:
515 value = git.discover_git_version(raise_on_exc=True)
515 value = git.discover_git_version(raise_on_exc=True)
516 human_value = 'version reported from VCSServer: {}'.format(value)
516 human_value = 'version reported from VCSServer: {}'.format(value)
517 except Exception as e:
517 except Exception as e:
518 state = {'message': str(e), 'type': STATE_ERR}
518 state = {'message': str(e), 'type': STATE_ERR}
519
519
520 return SysInfoRes(value=value, state=state, human_value=human_value)
520 return SysInfoRes(value=value, state=state, human_value=human_value)
521
521
522
522
523 def hg_info():
523 def hg_info():
524 from rhodecode.lib.vcs.backends import hg
524 from rhodecode.lib.vcs.backends import hg
525 state = STATE_OK_DEFAULT
525 state = STATE_OK_DEFAULT
526 value = human_value = ''
526 value = human_value = ''
527 try:
527 try:
528 value = hg.discover_hg_version(raise_on_exc=True)
528 value = hg.discover_hg_version(raise_on_exc=True)
529 human_value = 'version reported from VCSServer: {}'.format(value)
529 human_value = 'version reported from VCSServer: {}'.format(value)
530 except Exception as e:
530 except Exception as e:
531 state = {'message': str(e), 'type': STATE_ERR}
531 state = {'message': str(e), 'type': STATE_ERR}
532 return SysInfoRes(value=value, state=state, human_value=human_value)
532 return SysInfoRes(value=value, state=state, human_value=human_value)
533
533
534
534
535 def svn_info():
535 def svn_info():
536 from rhodecode.lib.vcs.backends import svn
536 from rhodecode.lib.vcs.backends import svn
537 state = STATE_OK_DEFAULT
537 state = STATE_OK_DEFAULT
538 value = human_value = ''
538 value = human_value = ''
539 try:
539 try:
540 value = svn.discover_svn_version(raise_on_exc=True)
540 value = svn.discover_svn_version(raise_on_exc=True)
541 human_value = 'version reported from VCSServer: {}'.format(value)
541 human_value = 'version reported from VCSServer: {}'.format(value)
542 except Exception as e:
542 except Exception as e:
543 state = {'message': str(e), 'type': STATE_ERR}
543 state = {'message': str(e), 'type': STATE_ERR}
544 return SysInfoRes(value=value, state=state, human_value=human_value)
544 return SysInfoRes(value=value, state=state, human_value=human_value)
545
545
546
546
547 def vcs_backends():
547 def vcs_backends():
548 import rhodecode
548 import rhodecode
549 value = rhodecode.CONFIG.get('vcs.backends')
549 value = rhodecode.CONFIG.get('vcs.backends')
550 human_value = 'Enabled backends in order: {}'.format(','.join(value))
550 human_value = 'Enabled backends in order: {}'.format(','.join(value))
551 return SysInfoRes(value=value, human_value=human_value)
551 return SysInfoRes(value=value, human_value=human_value)
552
552
553
553
554 def vcs_server():
554 def vcs_server():
555 import rhodecode
555 import rhodecode
556 from rhodecode.lib.vcs.backends import get_vcsserver_service_data
556 from rhodecode.lib.vcs.backends import get_vcsserver_service_data
557
557
558 server_url = rhodecode.CONFIG.get('vcs.server')
558 server_url = rhodecode.CONFIG.get('vcs.server')
559 enabled = rhodecode.CONFIG.get('vcs.server.enable')
559 enabled = rhodecode.CONFIG.get('vcs.server.enable')
560 protocol = rhodecode.CONFIG.get('vcs.server.protocol') or 'http'
560 protocol = rhodecode.CONFIG.get('vcs.server.protocol') or 'http'
561 state = STATE_OK_DEFAULT
561 state = STATE_OK_DEFAULT
562 version = None
562 version = None
563 workers = 0
563 workers = 0
564
564
565 try:
565 try:
566 data = get_vcsserver_service_data()
566 data = get_vcsserver_service_data()
567 if data and 'version' in data:
567 if data and 'version' in data:
568 version = data['version']
568 version = data['version']
569
569
570 if data and 'config' in data:
570 if data and 'config' in data:
571 conf = data['config']
571 conf = data['config']
572 workers = conf.get('workers', 'NOT AVAILABLE')
572 workers = conf.get('workers', 'NOT AVAILABLE')
573
573
574 connection = 'connected'
574 connection = 'connected'
575 except Exception as e:
575 except Exception as e:
576 connection = 'failed'
576 connection = 'failed'
577 state = {'message': str(e), 'type': STATE_ERR}
577 state = {'message': str(e), 'type': STATE_ERR}
578
578
579 value = dict(
579 value = dict(
580 url=server_url,
580 url=server_url,
581 enabled=enabled,
581 enabled=enabled,
582 protocol=protocol,
582 protocol=protocol,
583 connection=connection,
583 connection=connection,
584 version=version,
584 version=version,
585 text='',
585 text='',
586 )
586 )
587
587
588 human_value = value.copy()
588 human_value = value.copy()
589 human_value['text'] = \
589 human_value['text'] = \
590 '{url}@ver:{ver} via {mode} mode[workers:{workers}], connection:{conn}'.format(
590 '{url}@ver:{ver} via {mode} mode[workers:{workers}], connection:{conn}'.format(
591 url=server_url, ver=version, workers=workers, mode=protocol,
591 url=server_url, ver=version, workers=workers, mode=protocol,
592 conn=connection)
592 conn=connection)
593
593
594 return SysInfoRes(value=value, state=state, human_value=human_value)
594 return SysInfoRes(value=value, state=state, human_value=human_value)
595
595
596
596
597 def vcs_server_config():
598 from rhodecode.lib.vcs.backends import get_vcsserver_service_data
599 state = STATE_OK_DEFAULT
600
601 value = {}
602 try:
603 data = get_vcsserver_service_data()
604 value = data['app_config']
605 except Exception as e:
606 state = {'message': str(e), 'type': STATE_ERR}
607
608 human_value = value.copy()
609 human_value['text'] = 'VCS Server config'
610
611 return SysInfoRes(value=value, state=state, human_value=human_value)
612
613
597 def rhodecode_app_info():
614 def rhodecode_app_info():
598 import rhodecode
615 import rhodecode
599 edition = rhodecode.CONFIG.get('rhodecode.edition')
616 edition = rhodecode.CONFIG.get('rhodecode.edition')
600
617
601 value = dict(
618 value = dict(
602 rhodecode_version=rhodecode.__version__,
619 rhodecode_version=rhodecode.__version__,
603 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
620 rhodecode_lib_path=os.path.abspath(rhodecode.__file__),
604 text=''
621 text=''
605 )
622 )
606 human_value = value.copy()
623 human_value = value.copy()
607 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
624 human_value['text'] = 'RhodeCode {edition}, version {ver}'.format(
608 edition=edition, ver=value['rhodecode_version']
625 edition=edition, ver=value['rhodecode_version']
609 )
626 )
610 return SysInfoRes(value=value, human_value=human_value)
627 return SysInfoRes(value=value, human_value=human_value)
611
628
612
629
613 def rhodecode_config():
630 def rhodecode_config():
614 import rhodecode
631 import rhodecode
615 path = rhodecode.CONFIG.get('__file__')
632 path = rhodecode.CONFIG.get('__file__')
616 rhodecode_ini_safe = rhodecode.CONFIG.copy()
633 rhodecode_ini_safe = rhodecode.CONFIG.copy()
617 cert_path = get_cert_path(path)
634 cert_path = get_cert_path(path)
618
635
619 try:
636 try:
620 config = configparser.ConfigParser()
637 config = configparser.ConfigParser()
621 config.read(path)
638 config.read(path)
622 parsed_ini = config
639 parsed_ini = config
623 if parsed_ini.has_section('server:main'):
640 if parsed_ini.has_section('server:main'):
624 parsed_ini = dict(parsed_ini.items('server:main'))
641 parsed_ini = dict(parsed_ini.items('server:main'))
625 except Exception:
642 except Exception:
626 log.exception('Failed to read .ini file for display')
643 log.exception('Failed to read .ini file for display')
627 parsed_ini = {}
644 parsed_ini = {}
628
645
629 rhodecode_ini_safe['server:main'] = parsed_ini
646 rhodecode_ini_safe['server:main'] = parsed_ini
630
647
631 blacklist = [
648 blacklist = [
632 'rhodecode_license_key',
649 'rhodecode_license_key',
633 'routes.map',
650 'routes.map',
634 'sqlalchemy.db1.url',
651 'sqlalchemy.db1.url',
635 'channelstream.secret',
652 'channelstream.secret',
636 'beaker.session.secret',
653 'beaker.session.secret',
637 'rhodecode.encrypted_values.secret',
654 'rhodecode.encrypted_values.secret',
638 'rhodecode_auth_github_consumer_key',
655 'rhodecode_auth_github_consumer_key',
639 'rhodecode_auth_github_consumer_secret',
656 'rhodecode_auth_github_consumer_secret',
640 'rhodecode_auth_google_consumer_key',
657 'rhodecode_auth_google_consumer_key',
641 'rhodecode_auth_google_consumer_secret',
658 'rhodecode_auth_google_consumer_secret',
642 'rhodecode_auth_bitbucket_consumer_secret',
659 'rhodecode_auth_bitbucket_consumer_secret',
643 'rhodecode_auth_bitbucket_consumer_key',
660 'rhodecode_auth_bitbucket_consumer_key',
644 'rhodecode_auth_twitter_consumer_secret',
661 'rhodecode_auth_twitter_consumer_secret',
645 'rhodecode_auth_twitter_consumer_key',
662 'rhodecode_auth_twitter_consumer_key',
646
663
647 'rhodecode_auth_twitter_secret',
664 'rhodecode_auth_twitter_secret',
648 'rhodecode_auth_github_secret',
665 'rhodecode_auth_github_secret',
649 'rhodecode_auth_google_secret',
666 'rhodecode_auth_google_secret',
650 'rhodecode_auth_bitbucket_secret',
667 'rhodecode_auth_bitbucket_secret',
651
668
652 'appenlight.api_key',
669 'appenlight.api_key',
653 ('app_conf', 'sqlalchemy.db1.url')
670 ('app_conf', 'sqlalchemy.db1.url')
654 ]
671 ]
655 for k in blacklist:
672 for k in blacklist:
656 if isinstance(k, tuple):
673 if isinstance(k, tuple):
657 section, key = k
674 section, key = k
658 if section in rhodecode_ini_safe:
675 if section in rhodecode_ini_safe:
659 rhodecode_ini_safe[section] = '**OBFUSCATED**'
676 rhodecode_ini_safe[section] = '**OBFUSCATED**'
660 else:
677 else:
661 rhodecode_ini_safe.pop(k, None)
678 rhodecode_ini_safe.pop(k, None)
662
679
663 # TODO: maybe put some CONFIG checks here ?
680 # TODO: maybe put some CONFIG checks here ?
664 return SysInfoRes(value={'config': rhodecode_ini_safe,
681 return SysInfoRes(value={'config': rhodecode_ini_safe,
665 'path': path, 'cert_path': cert_path})
682 'path': path, 'cert_path': cert_path})
666
683
667
684
668 def database_info():
685 def database_info():
669 import rhodecode
686 import rhodecode
670 from sqlalchemy.engine import url as engine_url
687 from sqlalchemy.engine import url as engine_url
671 from rhodecode.model.meta import Base as sql_base, Session
688 from rhodecode.model.meta import Base as sql_base, Session
672 from rhodecode.model.db import DbMigrateVersion
689 from rhodecode.model.db import DbMigrateVersion
673
690
674 state = STATE_OK_DEFAULT
691 state = STATE_OK_DEFAULT
675
692
676 db_migrate = DbMigrateVersion.query().filter(
693 db_migrate = DbMigrateVersion.query().filter(
677 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
694 DbMigrateVersion.repository_id == 'rhodecode_db_migrations').one()
678
695
679 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
696 db_url_obj = engine_url.make_url(rhodecode.CONFIG['sqlalchemy.db1.url'])
680
697
681 try:
698 try:
682 engine = sql_base.metadata.bind
699 engine = sql_base.metadata.bind
683 db_server_info = engine.dialect._get_server_version_info(
700 db_server_info = engine.dialect._get_server_version_info(
684 Session.connection(bind=engine))
701 Session.connection(bind=engine))
685 db_version = '.'.join(map(str, db_server_info))
702 db_version = '.'.join(map(str, db_server_info))
686 except Exception:
703 except Exception:
687 log.exception('failed to fetch db version')
704 log.exception('failed to fetch db version')
688 db_version = 'UNKNOWN'
705 db_version = 'UNKNOWN'
689
706
690 db_info = dict(
707 db_info = dict(
691 migrate_version=db_migrate.version,
708 migrate_version=db_migrate.version,
692 type=db_url_obj.get_backend_name(),
709 type=db_url_obj.get_backend_name(),
693 version=db_version,
710 version=db_version,
694 url=repr(db_url_obj)
711 url=repr(db_url_obj)
695 )
712 )
696 current_version = db_migrate.version
713 current_version = db_migrate.version
697 expected_version = rhodecode.__dbversion__
714 expected_version = rhodecode.__dbversion__
698 if state['type'] == STATE_OK and current_version != expected_version:
715 if state['type'] == STATE_OK and current_version != expected_version:
699 msg = 'Critical: database schema mismatch, ' \
716 msg = 'Critical: database schema mismatch, ' \
700 'expected version {}, got {}. ' \
717 'expected version {}, got {}. ' \
701 'Please run migrations on your database.'.format(
718 'Please run migrations on your database.'.format(
702 expected_version, current_version)
719 expected_version, current_version)
703 state = {'message': msg, 'type': STATE_ERR}
720 state = {'message': msg, 'type': STATE_ERR}
704
721
705 human_value = db_info.copy()
722 human_value = db_info.copy()
706 human_value['url'] = "{} @ migration version: {}".format(
723 human_value['url'] = "{} @ migration version: {}".format(
707 db_info['url'], db_info['migrate_version'])
724 db_info['url'], db_info['migrate_version'])
708 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
725 human_value['version'] = "{} {}".format(db_info['type'], db_info['version'])
709 return SysInfoRes(value=db_info, state=state, human_value=human_value)
726 return SysInfoRes(value=db_info, state=state, human_value=human_value)
710
727
711
728
712 def server_info(environ):
729 def server_info(environ):
713 import rhodecode
730 import rhodecode
714 from rhodecode.lib.base import get_server_ip_addr, get_server_port
731 from rhodecode.lib.base import get_server_ip_addr, get_server_port
715
732
716 value = {
733 value = {
717 'server_ip': '%s:%s' % (
734 'server_ip': '%s:%s' % (
718 get_server_ip_addr(environ, log_errors=False),
735 get_server_ip_addr(environ, log_errors=False),
719 get_server_port(environ)
736 get_server_port(environ)
720 ),
737 ),
721 'server_id': rhodecode.CONFIG.get('instance_id'),
738 'server_id': rhodecode.CONFIG.get('instance_id'),
722 }
739 }
723 return SysInfoRes(value=value)
740 return SysInfoRes(value=value)
724
741
725
742
726 def usage_info():
743 def usage_info():
727 from rhodecode.model.db import User, Repository
744 from rhodecode.model.db import User, Repository
728 value = {
745 value = {
729 'users': User.query().count(),
746 'users': User.query().count(),
730 'users_active': User.query().filter(User.active == True).count(),
747 'users_active': User.query().filter(User.active == True).count(),
731 'repositories': Repository.query().count(),
748 'repositories': Repository.query().count(),
732 'repository_types': {
749 'repository_types': {
733 'hg': Repository.query().filter(
750 'hg': Repository.query().filter(
734 Repository.repo_type == 'hg').count(),
751 Repository.repo_type == 'hg').count(),
735 'git': Repository.query().filter(
752 'git': Repository.query().filter(
736 Repository.repo_type == 'git').count(),
753 Repository.repo_type == 'git').count(),
737 'svn': Repository.query().filter(
754 'svn': Repository.query().filter(
738 Repository.repo_type == 'svn').count(),
755 Repository.repo_type == 'svn').count(),
739 },
756 },
740 }
757 }
741 return SysInfoRes(value=value)
758 return SysInfoRes(value=value)
742
759
743
760
744 def get_system_info(environ):
761 def get_system_info(environ):
745 environ = environ or {}
762 environ = environ or {}
746 return {
763 return {
747 'rhodecode_app': SysInfo(rhodecode_app_info)(),
764 'rhodecode_app': SysInfo(rhodecode_app_info)(),
748 'rhodecode_config': SysInfo(rhodecode_config)(),
765 'rhodecode_config': SysInfo(rhodecode_config)(),
749 'rhodecode_usage': SysInfo(usage_info)(),
766 'rhodecode_usage': SysInfo(usage_info)(),
750 'python': SysInfo(python_info)(),
767 'python': SysInfo(python_info)(),
751 'py_modules': SysInfo(py_modules)(),
768 'py_modules': SysInfo(py_modules)(),
752
769
753 'platform': SysInfo(platform_type)(),
770 'platform': SysInfo(platform_type)(),
754 'locale': SysInfo(locale_info)(),
771 'locale': SysInfo(locale_info)(),
755 'server': SysInfo(server_info, environ=environ)(),
772 'server': SysInfo(server_info, environ=environ)(),
756 'database': SysInfo(database_info)(),
773 'database': SysInfo(database_info)(),
757 'ulimit': SysInfo(ulimit_info)(),
774 'ulimit': SysInfo(ulimit_info)(),
758 'storage': SysInfo(storage)(),
775 'storage': SysInfo(storage)(),
759 'storage_inodes': SysInfo(storage_inodes)(),
776 'storage_inodes': SysInfo(storage_inodes)(),
760 'storage_archive': SysInfo(storage_archives)(),
777 'storage_archive': SysInfo(storage_archives)(),
761 'storage_gist': SysInfo(storage_gist)(),
778 'storage_gist': SysInfo(storage_gist)(),
762 'storage_temp': SysInfo(storage_temp)(),
779 'storage_temp': SysInfo(storage_temp)(),
763
780
764 'search': SysInfo(search_info)(),
781 'search': SysInfo(search_info)(),
765
782
766 'uptime': SysInfo(uptime)(),
783 'uptime': SysInfo(uptime)(),
767 'load': SysInfo(machine_load)(),
784 'load': SysInfo(machine_load)(),
768 'cpu': SysInfo(cpu)(),
785 'cpu': SysInfo(cpu)(),
769 'memory': SysInfo(memory)(),
786 'memory': SysInfo(memory)(),
770
787
771 'vcs_backends': SysInfo(vcs_backends)(),
788 'vcs_backends': SysInfo(vcs_backends)(),
772 'vcs_server': SysInfo(vcs_server)(),
789 'vcs_server': SysInfo(vcs_server)(),
773
790
791 'vcs_server_config': SysInfo(vcs_server_config)(),
792
774 'git': SysInfo(git_info)(),
793 'git': SysInfo(git_info)(),
775 'hg': SysInfo(hg_info)(),
794 'hg': SysInfo(hg_info)(),
776 'svn': SysInfo(svn_info)(),
795 'svn': SysInfo(svn_info)(),
777 }
796 }
@@ -1,57 +1,71 b''
1
1
2 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
2 <div id="update_notice" style="display: none; margin: -40px 0px 20px 0px">
3 <div>${_('Checking for updates...')}</div>
3 <div>${_('Checking for updates...')}</div>
4 </div>
4 </div>
5
5
6
6
7 <div class="panel panel-default">
7 <div class="panel panel-default">
8 <div class="panel-heading">
8 <div class="panel-heading">
9 <h3 class="panel-title">${_('System Info')}</h3>
9 <h3 class="panel-title">${_('System Info')}</h3>
10 % if c.allowed_to_snapshot:
10 % if c.allowed_to_snapshot:
11 <a href="${h.route_path('admin_settings_system', _query={'snapshot':1})}" class="panel-edit">${_('create summary snapshot')}</a>
11 <a href="${h.route_path('admin_settings_system', _query={'snapshot':1})}" class="panel-edit">${_('create summary snapshot')}</a>
12 % endif
12 % endif
13 </div>
13 </div>
14 <div class="panel-body">
14 <div class="panel-body">
15 <dl class="dl-horizontal settings">
15 <dl class="dl-horizontal settings">
16 % for dt, dd, warn in c.data_items:
16 % for dt, dd, warn in c.data_items:
17 <dt>${dt}${':' if dt else '---'}</dt>
17 <dt>${dt}${':' if dt else '---'}</dt>
18 <dd>${dd}${'' if dt else '---'}
18 <dd>${dd}${'' if dt else '---'}
19 % if warn and warn['message']:
19 % if warn and warn['message']:
20 <div class="alert-${warn['type']}">
20 <div class="alert-${warn['type']}">
21 <strong>${warn['message']}</strong>
21 <strong>${warn['message']}</strong>
22 </div>
22 </div>
23 % endif
23 % endif
24 </dd>
24 </dd>
25 % endfor
25 % endfor
26 </dl>
26 </dl>
27 </div>
27 </div>
28 </div>
28 </div>
29
29
30 <div class="panel panel-default">
30 <div class="panel panel-default">
31 <div class="panel-heading">
31 <div class="panel-heading">
32 <h3 class="panel-title">${_('VCS Server')}</h3>
33 </div>
34 <div class="panel-body">
35 <dl class="dl-horizontal settings">
36 % for dt, dd in c.vcsserver_data_items:
37 <dt>${dt}${':' if dt else '---'}</dt>
38 <dd>${dd}${'' if dt else '---'}</dd>
39 % endfor
40 </dl>
41 </div>
42 </div>
43
44 <div class="panel panel-default">
45 <div class="panel-heading">
32 <h3 class="panel-title">${_('Python Packages')}</h3>
46 <h3 class="panel-title">${_('Python Packages')}</h3>
33 </div>
47 </div>
34 <div class="panel-body">
48 <div class="panel-body">
35 <table class="table">
49 <table class="table">
36 <colgroup>
50 <colgroup>
37 <col class='label'>
51 <col class='label'>
38 <col class='content'>
52 <col class='content'>
39 </colgroup>
53 </colgroup>
40 <tbody>
54 <tbody>
41 % for key, value in c.py_modules['human_value']:
55 % for key, value in c.py_modules['human_value']:
42 <tr>
56 <tr>
43 <td>${key}</td>
57 <td>${key}</td>
44 <td>${value}</td>
58 <td>${value}</td>
45 </tr>
59 </tr>
46 % endfor
60 % endfor
47 </tbody>
61 </tbody>
48 </table>
62 </table>
49 </div>
63 </div>
50 </div>
64 </div>
51
65
52 <script>
66 <script>
53 $('#check_for_update').click(function(e){
67 $('#check_for_update').click(function(e){
54 $('#update_notice').show();
68 $('#update_notice').show();
55 $('#update_notice').load("${h.route_path('admin_settings_system_update')}");
69 $('#update_notice').load("${h.route_path('admin_settings_system_update')}");
56 })
70 })
57 </script>
71 </script>
General Comments 0
You need to be logged in to leave comments. Login now