1 # Copyright (C) 2014 Ipsilon project Contributors, for license see COPYING
5 from ipsilon.util.page import Page
6 from ipsilon.util.page import admin_protect
7 from ipsilon.util.endpoint import allow_iframe
8 from ipsilon.util import config as pconfig
11 ADMIN_STATUS_OK = "success"
12 ADMIN_STATUS_ERROR = "danger"
13 ADMIN_STATUS_WARN = "warning"
16 class AdminError(Exception):
17 def __init__(self, message):
18 super(AdminError, self).__init__(message)
19 self.message = message
22 return str(self.message)
25 class AdminPage(Page):
27 def __init__(self, *args, **kwargs):
28 super(AdminPage, self).__init__(*args, **kwargs)
29 self.auth_protect = True
32 class AdminPluginConfig(AdminPage):
34 def __init__(self, po, site, parent):
35 super(AdminPluginConfig, self).__init__(site, form=True)
37 self.title = '%s plugin' % po.name
38 self.url = '%s/%s' % (parent.url, po.name)
39 self.facility = parent.facility
41 self.back = parent.url
43 def root_with_msg(self, message=None, message_type=None):
44 return self._template('admin/option_config.html', title=self.title,
45 menu=self.menu, action=self.url, back=self.back,
46 message=message, message_type=message_type,
47 name='admin_%s_%s_form' % (self.facility,
49 config=self._po.get_config_obj())
52 def GET(self, *args, **kwargs):
53 return self.root_with_msg()
56 def POST(self, *args, **kwargs):
58 if self._po.is_readonly:
59 return self.root_with_msg(
60 message="Configuration is marked Read-Only",
61 message_type=ADMIN_STATUS_WARN)
63 message = "Nothing was modified."
65 new_db_values = dict()
67 conf = self._po.get_config_obj()
69 for name, option in conf.iteritems():
72 if isinstance(option, pconfig.List):
73 value = [x.strip() for x in value.split('\n')]
74 elif isinstance(option, pconfig.Condition):
77 if isinstance(option, pconfig.Condition):
79 elif isinstance(option, pconfig.Choice):
81 for a in option.get_allowed():
82 aname = '%s_%s' % (name, a)
85 elif isinstance(option, pconfig.MappingList):
86 value = get_mapping_list_value(name,
91 elif isinstance(option, pconfig.ComplexList):
92 value = get_complex_list_value(name,
100 if value != option.get_value():
101 cherrypy.log.error("Storing [%s]: %s = %s" %
102 (self._po.name, name, value),
103 severity=logging.DEBUG)
104 option.set_value(value)
105 new_db_values[name] = option.export_value()
107 if len(new_db_values) != 0:
108 # First we try to save in the database
110 self._po.save_plugin_config(new_db_values)
111 message = "New configuration saved."
112 message_type = ADMIN_STATUS_OK
113 except Exception as e: # pylint: disable=broad-except
114 self.error('Failed to save data: %s' % e)
115 message = "Failed to save data!"
116 message_type = ADMIN_STATUS_ERROR
118 # Then refresh the actual objects
119 self._po.refresh_plugin_config()
121 return self.root_with_msg(message=message,
122 message_type=message_type)
125 class AdminPluginsOrder(AdminPage):
127 def __init__(self, site, parent, facility):
128 super(AdminPluginsOrder, self).__init__(site, form=True)
130 self.facility = facility
131 self.url = '%s/order' % parent.url
135 def GET(self, *args, **kwargs):
136 return self.parent.root_with_msg()
139 def POST(self, *args, **kwargs):
141 if self._site[self.facility].is_readonly:
142 return self.parent.root_with_msg(
143 message="Configuration is marked Read-Only",
144 message_type=ADMIN_STATUS_WARN)
146 message = "Nothing was modified."
147 message_type = "info"
149 cur_enabled = self._site[self.facility].enabled
151 if 'order' in kwargs:
152 order = kwargs['order'].split(',')
158 if val not in cur_enabled:
159 error = "Invalid plugin name: %s" % val
160 raise ValueError(error)
161 new_order.append(val)
162 if len(new_order) < len(cur_enabled):
163 for val in cur_enabled:
164 if val not in new_order:
165 new_order.append(val)
167 self.parent.save_enabled_plugins(new_order)
169 # When all is saved update also live config. The
170 # live config is the ordered list of plugin names.
171 self._site[self.facility].refresh_enabled()
173 message = "New configuration saved."
174 message_type = ADMIN_STATUS_OK
177 self.debug('%s -> %s' % (cur_enabled, new_order))
178 for i in range(0, len(cur_enabled)):
179 if cur_enabled[i] != new_order[i]:
180 changed[cur_enabled[i]] = 'reordered'
182 except ValueError, e:
184 message_type = ADMIN_STATUS_ERROR
186 except Exception as e: # pylint: disable=broad-except
187 self.error('Failed to save data: %s' % e)
188 message = "Failed to save data!"
189 message_type = ADMIN_STATUS_ERROR
191 return self.parent.root_with_msg(message=message,
192 message_type=message_type,
196 class AdminPlugins(AdminPage):
197 def __init__(self, name, site, parent, facility, ordered=True):
198 super(AdminPlugins, self).__init__(site)
199 self._master = parent
201 self.title = '%s plugins' % name
202 self.url = '%s/%s' % (parent.url, name)
203 self.facility = facility
204 self.template = 'admin/plugins.html'
206 parent.add_subtree(name, self)
208 if self._site[facility] is None:
211 for plugin in self._site[facility].available:
212 cherrypy.log.error('Admin info plugin: %s' % plugin,
213 severity=logging.DEBUG)
214 obj = self._site[facility].available[plugin]
215 page = AdminPluginConfig(obj, self._site, self)
216 if hasattr(obj, 'admin'):
217 obj.admin.mount(page)
218 self.add_subtree(plugin, page)
221 self.order = AdminPluginsOrder(self._site, self, facility)
223 def save_enabled_plugins(self, names):
224 self._site[self.facility].save_enabled(names)
226 def root_with_msg(self, message=None, message_type=None, changed=None):
227 plugins = self._site[self.facility]
232 targs = {'title': self.title,
233 'menu': self._master.menu,
235 'message_type': message_type,
236 'available': plugins.available,
237 'enabled': plugins.enabled,
242 targs['order_name'] = '%s_order_form' % self.name
243 targs['order_action'] = self.order.url
245 return self._template(self.template, **targs)
247 def root(self, *args, **kwargs):
248 return self.root_with_msg()
250 def _get_plugin_obj(self, plugin):
251 plugins = self._site[self.facility]
252 if plugins.is_readonly:
253 msg = "Configuration is marked Read-Only"
254 raise AdminError(msg)
255 if plugin not in plugins.available:
256 msg = "Unknown plugin %s" % plugin
257 raise AdminError(msg)
258 obj = plugins.available[plugin]
260 msg = "Plugin Configuration is marked Read-Only"
261 raise AdminError(msg)
265 def enable(self, plugin):
268 obj = self._get_plugin_obj(plugin)
269 except AdminError, e:
270 return self.root_with_msg(str(e), ADMIN_STATUS_WARN)
271 if not obj.is_enabled:
274 except Exception as e: # pylint: disable=broad-except
275 return self.root_with_msg(str(e), ADMIN_STATUS_WARN)
276 obj.save_enabled_state()
277 msg = "Plugin %s enabled" % obj.name
278 return self.root_with_msg(msg, ADMIN_STATUS_OK,
279 changed={obj.name: 'enabled'})
280 enable.public_function = True
283 def disable(self, plugin):
286 obj = self._get_plugin_obj(plugin)
287 except AdminError, e:
288 return self.root_with_msg(str(e), ADMIN_STATUS_WARN)
292 except Exception as e: # pylint: disable=broad-except
293 return self.root_with_msg(str(e), ADMIN_STATUS_WARN)
294 obj.save_enabled_state()
295 msg = "Plugin %s disabled" % obj.name
296 return self.root_with_msg(msg, ADMIN_STATUS_OK,
297 changed={obj.name: 'disabled'})
298 disable.public_function = True
301 class Admin(AdminPage):
303 def __init__(self, site, mount):
304 super(Admin, self).__init__(site)
307 self.url = '%s/%s' % (self.basepath, mount)
310 def root(self, *args, **kwargs):
311 return self._template('admin/index.html',
312 title='Configuration',
316 def add_subtree(self, name, page):
317 self.__dict__[name] = page
318 self.menu.append(page)
320 def del_subtree(self, name):
321 self.menu.remove(self.__dict__[name])
322 del self.__dict__[name]
324 def get_menu_urls(self):
326 for item in self.menu:
327 name = getattr(item, 'name', None)
329 urls['%s_url' % name] = cherrypy.url('/%s/%s' % (self.mount,
336 cherrypy.response.headers.update({'Content-Type': 'image/svg+xml'})
337 urls = self.get_menu_urls()
338 return str(self._template('admin/ipsilon-scheme.svg', **urls))
339 scheme.public_function = True
342 def get_complex_list_value(name, old_value, **kwargs):
345 for key, val in kwargs.iteritems():
346 if not key.startswith(name):
349 if len(n) == 0 or n[0] != ' ':
352 index, field = n[1:].split('-')
355 if field == 'delete':
356 delete.append(int(index))
357 elif field == 'name':
358 change[int(index)] = val
360 if len(delete) == 0 and len(change) == 0:
365 # remove unwanted changes
370 # perform requested changes
371 for index, val in change.iteritems():
372 val_list = val.split('/')
375 stripped.append(v.strip())
376 if len(stripped) == 1:
377 stripped = stripped[0]
378 if len(value) <= index:
379 value.extend([None]*(index + 1 - len(value)))
380 value[index] = stripped
382 if len(value[index]) == 0:
385 # the previous loop may add 'None' entries
386 # if any still exists mark them to be deleted
387 for i in xrange(0, len(value)):
391 # remove duplicates and set in reverse order
392 delete = list(set(delete))
393 delete.sort(reverse=True)
404 def get_mapping_list_value(name, old_value, **kwargs):
407 for key, val in kwargs.iteritems():
408 if not key.startswith(name):
411 if len(n) == 0 or n[0] != ' ':
414 index, field = n[1:].split('-')
417 if field == 'delete':
418 delete.append(int(index))
423 change[i][field] = val
425 if len(delete) == 0 and len(change) == 0:
430 # remove unwanted changes
435 # perform requested changes
436 for index, fields in change.iteritems():
437 for k in 'from', 'to':
440 val_list = val.split('/')
443 stripped.append(v.strip())
444 if len(stripped) == 1:
445 stripped = stripped[0]
446 if len(value) <= index:
447 value.extend([None]*(index + 1 - len(value)))
448 if value[index] is None:
449 value[index] = [None, None]
454 value[index][i] = stripped
456 # eliminate incomplete/incorrect entries
457 if value[index] is not None:
458 if ((len(value[index]) != 2 or
459 value[index][0] is None or
460 len(value[index][0]) == 0 or
461 value[index][1] is None or
462 len(value[index][1]) == 0)):
465 # the previous loop may add 'None' entries
466 # if any still exists mark them to be deleted
467 for i in xrange(0, len(value)):
471 # remove duplicates and set in reverse order
472 delete = list(set(delete))
473 delete.sort(reverse=True)