Source code for tvb.interfaces.web.controllers.settings_controller

# -*- coding: utf-8 -*-
# TheVirtualBrain-Framework Package. This package holds all Data Management, and 
# Web-UI helpful to run brain-simulations. To use it, you also need to download
# TheVirtualBrain-Scientific Package (for simulators). See content of the
# documentation-folder for more details. See also
# (c) 2012-2023, Baycrest Centre for Geriatric Care ("Baycrest") and others
# This program is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software Foundation,
# either version 3 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along with this
# program.  If not, see <>.
# When using The Virtual Brain for scientific publications, please cite it as explained here:

.. moduleauthor:: Bogdan Neacsa <>

import os
import subprocess
import threading
import cherrypy
import formencode
from time import sleep
from formencode import validators
from tvb.basic.profile import TvbProfile
from import InvalidSettingsException
from import SettingsService
from tvb.interfaces.web.controllers import common
from tvb.interfaces.web.controllers.autologging import traced
from tvb.interfaces.web.controllers.decorators import check_admin, using_template, jsonify, handle_error
from tvb.interfaces.web.controllers.users_controller import UserController

[docs]@traced class SettingsController(UserController): """ Controller for TVB-Settings web page. Inherit from UserController, to have the same fill_default_attributes method (with versionInfo). """ def __init__(self): UserController.__init__(self) self.settingsservice = SettingsService()
[docs] @cherrypy.expose @handle_error(redirect=True) @using_template('user/base_user') @check_admin def settings(self, save_settings=False, **data): """Main settings page submit and get""" template_specification = dict(mainContent="settings/system_settings", title="System Settings") if save_settings: try: form = SettingsForm() data = form.to_python(data) isrestart, isreset = self.settingsservice.save_settings(**data) if isrestart: thread = threading.Thread(target=self._restart_services, kwargs={'should_reset': isreset}) thread.start() common.add2session(common.KEY_IS_RESTART, True) common.set_important_message('Please wait until TVB is restarted properly!') self.redirect('/tvb') # Here we will leave the same settings page to be displayed. # It will continue reloading when CherryPy restarts. except formencode.Invalid as excep: template_specification[common.KEY_ERRORS] = excep.unpack_errors() except InvalidSettingsException as excep: self.logger.error('Invalid settings! Exception %s was raised' % (str(excep))) common.set_error_message(excep.message) template_specification.update({'keys_order': self.settingsservice.KEYS_DISPLAY_ORDER, 'config_data': self.settingsservice.configurable_keys, common.KEY_FIRST_RUN: TvbProfile.is_first_run()}) return self.fill_default_attributes(template_specification)
def _restart_services(self, should_reset): """ Restart CherryPy Backend. """ sleep(1) cherrypy.engine.exit()"Waiting for CherryPy to shut down ... ") sleep(5) python_path = TvbProfile.current.PYTHON_INTERPRETER_PATH try: import tvb_bin proc_params = [python_path, '-m', '', 'start', TvbProfile.CURRENT_PROFILE_NAME] if should_reset: proc_params.append('-reset') subprocess.Popen(proc_params, shell=False) except ImportError: proc_params = [python_path, '-m', '', TvbProfile.CURRENT_PROFILE_NAME, "tvb.config"] if should_reset: proc_params.append('reset') subprocess.Popen(proc_params, shell=False).communicate()"Starting CherryPy again ... ")
[docs] @cherrypy.expose @handle_error(redirect=False) @jsonify def check_db_url(self, **data): """ Action on DB-URL validate button. """ try: storage_path = data[self.settingsservice.KEY_STORAGE] if os.path.isfile(storage_path): raise InvalidSettingsException('TVB Storage should be set to a folder and not a file.') if not os.path.isdir(storage_path): try: os.mkdir(storage_path) except OSError: return {'status': 'not ok', 'message': 'Could not create root storage for TVB. Please check write permissions!'} self.settingsservice.check_db_url(data[self.settingsservice.KEY_DB_URL]) return {'status': 'ok', 'message': 'The database URL is valid.'} except InvalidSettingsException as excep: self.logger.error(excep) return {'status': 'not ok', 'message': 'The database URL is not valid.'}
[docs]class DiskSpaceValidator(formencode.FancyValidator): """ Custom validator for TVB disk space / user. """ def _convert_to_python(self, value, _): """ Validation required method. :param value is user-specified value, in MB """ try: value = int(value) return value except ValueError: raise formencode.Invalid('Invalid disk space %s. Should be number' % value, value, None)
[docs]class PortValidator(formencode.FancyValidator): """ Custom validator for OS Port number. """ def _convert_to_python(self, value, _): """ Validation required method. """ try: value = int(value) except ValueError: raise formencode.Invalid('Invalid port %s. Should be number between 0 and 65535.' % value, value, None) if 0 < value < 65535: return value else: raise formencode.Invalid('Invalid port number %s. Should be in interval [0, 65535]' % value, value, None)
[docs]class ThreadNrValidator(formencode.FancyValidator): """ Custom validator number of threads. """ def _convert_to_python(self, value, _): """ Validation required method. """ try: value = int(value) except ValueError: raise formencode.Invalid('Invalid number %s. Should be number between 1 and 16.' % value, value, None) if 0 < value < 17: return value else: raise formencode.Invalid('Invalid number %d. Should be in interval [1, 16]' % value, value, None)
[docs]class SurfaceVerticesNrValidator(formencode.FancyValidator): """ Custom validator for the number of vertices allowed for a surface """ # This limitation is given by our Max number of colors in pick mechanism MAX_VALUE = 256 * 256 * 256 + 1 def _convert_to_python(self, value, _): """ Validation required method. """ msg = 'Invalid value: %s. Should be a number between 1 and %d.' try: value = int(value) if 0 < value < self.MAX_VALUE: return value else: raise formencode.Invalid(msg % (str(value), self.MAX_VALUE), value, None) except ValueError: raise formencode.Invalid(msg % (value, self.MAX_VALUE), value, None)
[docs]class AsciiValidator(formencode.FancyValidator): """ Allow only ascii strings """ def _convert_to_python(self, value, _): try: return str(value).encode('ascii') except UnicodeError: raise formencode.Invalid('Invalid ascii string %s' % value, '', None)
[docs]class SettingsForm(formencode.Schema): """ Validate Settings Page inputs. """ ADMINISTRATOR_NAME = formencode.All(validators.UnicodeString(not_empty=True), validators.PlainText()) ADMINISTRATOR_DISPLAY_NAME = formencode.All(validators.UnicodeString(not_empty=True), validators.PlainText()) ADMINISTRATOR_PASSWORD = validators.UnicodeString(not_empty=True) ADMINISTRATOR_EMAIL = validators.Email(not_empty=True) WEB_SERVER_PORT = PortValidator() SELECTED_DB = validators.UnicodeString(not_empty=True) URL_VALUE = validators.UnicodeString(not_empty=True) DEPLOY_CLUSTER = validators.Bool() CLUSTER_SCHEDULER = validators.UnicodeString(not_empty=True) KEYCLOAK_CONFIGURATION = validators.UnicodeString() KEYCLOAK_WEB_CONFIGURATION = validators.UnicodeString() ENABLE_KEYCLOAK_LOGIN = validators.Bool() TVB_STORAGE = validators.UnicodeString(not_empty=True) USR_DISK_SPACE = DiskSpaceValidator(not_empty=True) MAXIMUM_NR_OF_THREADS = ThreadNrValidator() MAXIMUM_NR_OF_VERTICES_ON_SURFACE = SurfaceVerticesNrValidator() MAXIMUM_NR_OF_OPS_IN_RANGE = validators.Int(min=5, max=5000, not_empty=True)