X-Git-Url: http://git.cascardo.info/?p=cascardo%2Fipsilon.git;a=blobdiff_plain;f=ipsilon%2Fadmin%2Fcommon.py;h=87bfcd52a4e096016ec626db2a28d89aef90a6bb;hp=b9dfbf44ab4288153d708fbb8331736453e64c20;hb=aa5dc3b417db962a075a092d0d3528010c1059f7;hpb=0167e69a38734586c1a1f45786313efb3b5f73c3 diff --git a/ipsilon/admin/common.py b/ipsilon/admin/common.py old mode 100755 new mode 100644 index b9dfbf4..87bfcd5 --- a/ipsilon/admin/common.py +++ b/ipsilon/admin/common.py @@ -1,5 +1,3 @@ -#!/usr/bin/python -# # Copyright (C) 2014 Simo Sorce # # see file 'COPYING' for use and warranty information @@ -18,20 +16,31 @@ # along with this program. If not, see . import cherrypy +import logging from ipsilon.util.page import Page from ipsilon.util.page import admin_protect -from ipsilon.util.plugin import PluginObject +from ipsilon.util.endpoint import allow_iframe +from ipsilon.util import config as pconfig + + +ADMIN_STATUS_OK = "success" +ADMIN_STATUS_ERROR = "danger" +ADMIN_STATUS_WARN = "warning" + + +class AdminError(Exception): + def __init__(self, message): + super(AdminError, self).__init__(message) + self.message = message + + def __str__(self): + return str(self.message) class AdminPage(Page): def __init__(self, *args, **kwargs): super(AdminPage, self).__init__(*args, **kwargs) - self.default_headers.update({ - 'Cache-Control': 'no-cache, must-revalidate', - 'Pragma': 'no-cache', - 'Expires': 'Thu, 01 Dec 1994 16:00:00 GMT', - }) self.auth_protect = True @@ -46,33 +55,13 @@ class AdminPluginConfig(AdminPage): self.menu = [parent] self.back = parent.url - # Get the defaults - options = po.get_config_desc() - if options is None: - options = dict() - - self.options_order = [] - if hasattr(po, 'conf_opt_order'): - self.options_order = po.conf_opt_order - - # append any undefined options - add = [] - for k in options.keys(): - if k not in self.options_order: - add.append(k) - if len(add): - add.sort() - for k in add: - self.options_order.append(k) - def root_with_msg(self, message=None, message_type=None): - return self._template('admin/plugin_config.html', title=self.title, + return self._template('admin/option_config.html', title=self.title, menu=self.menu, action=self.url, back=self.back, message=message, message_type=message_type, name='admin_%s_%s_form' % (self.facility, self._po.name), - options_order=self.options_order, - plugin=self._po) + config=self._po.get_config_obj()) @admin_protect def GET(self, *args, **kwargs): @@ -81,34 +70,68 @@ class AdminPluginConfig(AdminPage): @admin_protect def POST(self, *args, **kwargs): + if self._po.is_readonly: + return self.root_with_msg( + message="Configuration is marked Read-Only", + message_type=ADMIN_STATUS_WARN) + message = "Nothing was modified." message_type = "info" - new_values = dict() - - # Get the defaults - options = self._po.get_config_desc() - if options is None: - options = dict() - - for key, value in kwargs.iteritems(): - if key in options: - if value != self._po.get_config_value(key): - cherrypy.log.error("Storing [%s]: %s = %s" % - (self._po.name, key, value)) - new_values[key] = value - - if len(new_values) != 0: + new_db_values = dict() + + conf = self._po.get_config_obj() + + for name, option in conf.iteritems(): + if name in kwargs: + value = kwargs[name] + if isinstance(option, pconfig.List): + value = [x.strip() for x in value.split('\n')] + elif isinstance(option, pconfig.Condition): + value = True + else: + if isinstance(option, pconfig.Condition): + value = False + elif isinstance(option, pconfig.Choice): + value = list() + for a in option.get_allowed(): + aname = '%s_%s' % (name, a) + if aname in kwargs: + value.append(a) + elif type(option) is pconfig.ComplexList: + value = get_complex_list_value(name, + option.get_value(), + **kwargs) + if value is None: + continue + elif type(option) is pconfig.MappingList: + value = get_mapping_list_value(name, + option.get_value(), + **kwargs) + if value is None: + continue + else: + continue + + if value != option.get_value(): + cherrypy.log.error("Storing [%s]: %s = %s" % + (self._po.name, name, value), + severity=logging.DEBUG) + option.set_value(value) + new_db_values[name] = option.export_value() + + if len(new_db_values) != 0: # First we try to save in the database try: - self._po.save_plugin_config(self.facility, new_values) + self._po.save_plugin_config(new_db_values) message = "New configuration saved." - message_type = "success" - except Exception: # pylint: disable=broad-except + message_type = ADMIN_STATUS_OK + except Exception as e: # pylint: disable=broad-except + self.error('Failed to save data: %s' % e) message = "Failed to save data!" - message_type = "error" + message_type = ADMIN_STATUS_ERROR - # And only if it succeeds we change the live object - self._po.refresh_plugin_config(self.facility) + # Then refresh the actual objects + self._po.refresh_plugin_config() return self.root_with_msg(message=message, message_type=message_type) @@ -127,59 +150,62 @@ class AdminPluginsOrder(AdminPage): def GET(self, *args, **kwargs): return self.parent.root_with_msg() - def _get_enabled_by_name(self): - by_name = dict() - for p in self._site[self.facility]['available'].values(): - if p.is_enabled: - by_name[p.name] = p - return by_name - @admin_protect def POST(self, *args, **kwargs): + + if self._site[self.facility].is_readonly: + return self.parent.root_with_msg( + message="Configuration is marked Read-Only", + message_type=ADMIN_STATUS_WARN) + message = "Nothing was modified." message_type = "info" - by_name = self._get_enabled_by_name() + changed = None + cur_enabled = self._site[self.facility].enabled if 'order' in kwargs: order = kwargs['order'].split(',') if len(order) != 0: - new_names = [] - new_plugins = [] + new_order = [] try: for v in order: val = v.strip() - if val not in by_name: + if val not in cur_enabled: error = "Invalid plugin name: %s" % val raise ValueError(error) - new_names.append(val) - new_plugins.append(by_name[val]) - if len(new_names) < len(by_name): - for val in by_name: - if val not in new_names: - new_names.append(val) - new_plugins.append(by_name[val]) + new_order.append(val) + if len(new_order) < len(cur_enabled): + for val in cur_enabled: + if val not in new_order: + new_order.append(val) - self.parent.save_enabled_plugins(new_names) - self.parent.reorder_plugins(new_names) + self.parent.save_enabled_plugins(new_order) # When all is saved update also live config. The - # live config is a list of the actual plugin - # objects. - self._site[self.facility]['enabled'] = new_plugins + # live config is the ordered list of plugin names. + self._site[self.facility].refresh_enabled() message = "New configuration saved." - message_type = "success" + message_type = ADMIN_STATUS_OK + + changed = dict() + self.debug('%s -> %s' % (cur_enabled, new_order)) + for i in range(0, len(cur_enabled)): + if cur_enabled[i] != new_order[i]: + changed[cur_enabled[i]] = 'reordered' except ValueError, e: message = str(e) - message_type = "error" + message_type = ADMIN_STATUS_ERROR - except Exception, e: # pylint: disable=broad-except + except Exception as e: # pylint: disable=broad-except + self.error('Failed to save data: %s' % e) message = "Failed to save data!" - message_type = "error" + message_type = ADMIN_STATUS_ERROR return self.parent.root_with_msg(message=message, - message_type=message_type) + message_type=message_type, + changed=changed) class AdminPlugins(AdminPage): @@ -194,9 +220,13 @@ class AdminPlugins(AdminPage): self.order = None parent.add_subtree(name, self) - for plugin in self._site[facility]['available']: - cherrypy.log.error('Admin info plugin: %s' % plugin) - obj = self._site[facility]['available'][plugin] + if self._site[facility] is None: + return + + for plugin in self._site[facility].available: + cherrypy.log.error('Admin info plugin: %s' % plugin, + severity=logging.DEBUG) + obj = self._site[facility].available[plugin] page = AdminPluginConfig(obj, self._site, self) if hasattr(obj, 'admin'): obj.admin.mount(page) @@ -206,35 +236,23 @@ class AdminPlugins(AdminPage): self.order = AdminPluginsOrder(self._site, self, facility) def save_enabled_plugins(self, names): - po = PluginObject() - po.name = "global" - globalconf = dict() - globalconf['order'] = ','.join(names) - po.set_config(globalconf) - po.save_plugin_config(self.facility) - - def reorder_plugins(self, names): - return + self._site[self.facility].save_enabled(names) - def root_with_msg(self, message=None, message_type=None): + def root_with_msg(self, message=None, message_type=None, changed=None): plugins = self._site[self.facility] - enabled = [] - if self.order: - for plugin in plugins['enabled']: - if plugin.is_enabled: - enabled.append(plugin.name) - else: - for _, plugin in plugins['available'].iteritems(): - if plugin.is_enabled: - enabled.append(plugin.name) + + if changed is None: + changed = dict() targs = {'title': self.title, 'menu': self._master.menu, 'message': message, 'message_type': message_type, - 'available': plugins['available'], - 'enabled': enabled, - 'baseurl': self.url} + 'available': plugins.available, + 'enabled': plugins.enabled, + 'changed': changed, + 'baseurl': self.url, + 'newurl': self.url} if self.order: targs['order_name'] = '%s_order_form' % self.name targs['order_action'] = self.order.url @@ -245,38 +263,54 @@ class AdminPlugins(AdminPage): def root(self, *args, **kwargs): return self.root_with_msg() + def _get_plugin_obj(self, plugin): + plugins = self._site[self.facility] + if plugins.is_readonly: + msg = "Configuration is marked Read-Only" + raise AdminError(msg) + if plugin not in plugins.available: + msg = "Unknown plugin %s" % plugin + raise AdminError(msg) + obj = plugins.available[plugin] + if obj.is_readonly: + msg = "Plugin Configuration is marked Read-Only" + raise AdminError(msg) + return obj + @admin_protect def enable(self, plugin): msg = None - plugins = self._site[self.facility] - if plugin not in plugins['available']: - msg = "Unknown plugin %s" % plugin - return self.root_with_msg(msg, "error") - obj = plugins['available'][plugin] + try: + obj = self._get_plugin_obj(plugin) + except AdminError, e: + return self.root_with_msg(str(e), ADMIN_STATUS_WARN) if not obj.is_enabled: - obj.enable(self._site) - if self.order: - enabled = list(x.name for x in plugins['enabled']) - self.save_enabled_plugins(enabled) + try: + obj.enable() + except Exception as e: # pylint: disable=broad-except + return self.root_with_msg(str(e), ADMIN_STATUS_WARN) + obj.save_enabled_state() msg = "Plugin %s enabled" % obj.name - return self.root_with_msg(msg, "success") + return self.root_with_msg(msg, ADMIN_STATUS_OK, + changed={obj.name: 'enabled'}) enable.public_function = True @admin_protect def disable(self, plugin): msg = None - plugins = self._site[self.facility] - if plugin not in plugins['available']: - msg = "Unknown plugin %s" % plugin - return self.root_with_msg(msg, "error") - obj = plugins['available'][plugin] + try: + obj = self._get_plugin_obj(plugin) + except AdminError, e: + return self.root_with_msg(str(e), ADMIN_STATUS_WARN) if obj.is_enabled: - obj.disable(self._site) - if self.order: - enabled = list(x.name for x in plugins['enabled']) - self.save_enabled_plugins(enabled) + try: + obj.disable() + except Exception as e: # pylint: disable=broad-except + return self.root_with_msg(str(e), ADMIN_STATUS_WARN) + obj.save_enabled_state() msg = "Plugin %s disabled" % obj.name - return self.root_with_msg(msg, "success") + return self.root_with_msg(msg, ADMIN_STATUS_OK, + changed={obj.name: 'disabled'}) disable.public_function = True @@ -313,9 +347,152 @@ class Admin(AdminPage): return urls @admin_protect + @allow_iframe def scheme(self): cherrypy.response.headers.update({'Content-Type': 'image/svg+xml'}) urls = self.get_menu_urls() # pylint: disable=star-args - return self._template('admin/ipsilon-scheme.svg', **urls) + return str(self._template('admin/ipsilon-scheme.svg', **urls)) scheme.public_function = True + + +def get_complex_list_value(name, old_value, **kwargs): + delete = list() + change = dict() + for key, val in kwargs.iteritems(): + if not key.startswith(name): + continue + n = key[len(name):] + if len(n) == 0 or n[0] != ' ': + continue + try: + index, field = n[1:].split('-') + except ValueError: + continue + if field == 'delete': + delete.append(int(index)) + elif field == 'name': + change[int(index)] = val + + if len(delete) == 0 and len(change) == 0: + return None + + value = old_value + + # remove unwanted changes + for i in delete: + if i in change: + del change[i] + + # perform requested changes + for index, val in change.iteritems(): + val_list = val.split('/') + stripped = list() + for v in val_list: + stripped.append(v.strip()) + if len(stripped) == 1: + stripped = stripped[0] + if len(value) <= index: + value.extend([None]*(index + 1 - len(value))) + value[index] = stripped + + if len(value[index]) == 0: + value[index] = None + + # the previous loop may add 'None' entries + # if any still exists mark them to be deleted + for i in xrange(0, len(value)): + if value[i] is None: + delete.append(i) + + # remove duplicates and set in reverse order + delete = list(set(delete)) + delete.sort(reverse=True) + + for i in delete: + if len(value) > i: + del value[i] + + if len(value) == 0: + value = None + return value + + +def get_mapping_list_value(name, old_value, **kwargs): + delete = list() + change = dict() + for key, val in kwargs.iteritems(): + if not key.startswith(name): + continue + n = key[len(name):] + if len(n) == 0 or n[0] != ' ': + continue + try: + index, field = n[1:].split('-') + except ValueError: + continue + if field == 'delete': + delete.append(int(index)) + else: + i = int(index) + if i not in change: + change[i] = dict() + change[i][field] = val + + if len(delete) == 0 and len(change) == 0: + return None + + value = old_value + + # remove unwanted changes + for i in delete: + if i in change: + del change[i] + + # perform requested changes + for index, fields in change.iteritems(): + for k in 'from', 'to': + if k in fields: + val = fields[k] + val_list = val.split('/') + stripped = list() + for v in val_list: + stripped.append(v.strip()) + if len(stripped) == 1: + stripped = stripped[0] + if len(value) <= index: + value.extend([None]*(index + 1 - len(value))) + if value[index] is None: + value[index] = [None, None] + if k == 'from': + i = 0 + else: + i = 1 + value[index][i] = stripped + + # eliminate incomplete/incorrect entries + if value[index] is not None: + if ((len(value[index]) != 2 or + value[index][0] is None or + len(value[index][0]) == 0 or + value[index][1] is None or + len(value[index][1]) == 0)): + value[index] = None + + # the previous loop may add 'None' entries + # if any still exists mark them to be deleted + for i in xrange(0, len(value)): + if value[i] is None: + delete.append(i) + + # remove duplicates and set in reverse order + delete = list(set(delete)) + delete.sort(reverse=True) + + for i in delete: + if len(value) > i: + del value[i] + + if len(value) == 0: + value = None + return value