-#!/usr/bin/python
-#
# Copyright (C) 2014 Simo Sorce <simo@redhat.com>
#
# see file 'COPYING' for use and warranty information
import cherrypy
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
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):
@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))
+ 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)
@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 = {p.name: p for p in self._site[self.facility]['enabled']}
+ 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):
self.order = None
parent.add_subtree(name, self)
- for plugin in self._site[facility]['available']:
+ if self._site[facility] is None:
+ return
+
+ for plugin in self._site[facility].available:
cherrypy.log.error('Admin info plugin: %s' % plugin)
- obj = self._site[facility]['available'][plugin]
+ obj = self._site[facility].available[plugin]
page = AdminPluginConfig(obj, self._site, self)
if hasattr(obj, 'admin'):
obj.admin.mount(page)
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']:
- 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
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
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