summaryrefslogtreecommitdiffstats
path: root/ipawebui
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2009-12-09 09:09:53 -0700
committerJason Gerard DeRose <jderose@redhat.com>2009-12-10 08:29:15 -0700
commitb6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7 (patch)
tree7e5329a51af169ce34a7d275a1bbd63c1e31c026 /ipawebui
parentd08b8858ddc3bf6265f6ea8acae6661b9fff5112 (diff)
downloadfreeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.tar.gz
freeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.tar.xz
freeipa-b6e4972e7f6aa08e0392a2cf441b60ab0e7d88b7.zip
Take 2: Extensible return values and validation; steps toward a single output_for_cli(); enable more webUI stuff
Diffstat (limited to 'ipawebui')
-rw-r--r--ipawebui/__init__.py2
-rw-r--r--ipawebui/engine.py56
-rw-r--r--ipawebui/widgets.py186
3 files changed, 222 insertions, 22 deletions
diff --git a/ipawebui/__init__.py b/ipawebui/__init__.py
index 9f86da344..c7ebaa870 100644
--- a/ipawebui/__init__.py
+++ b/ipawebui/__init__.py
@@ -1,6 +1,6 @@
# Authors: Jason Gerard DeRose <jderose@redhat.com>
#
-# Copyright (C) 2008 Red Hat
+# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
diff --git a/ipawebui/engine.py b/ipawebui/engine.py
index ea0905d49..a90a450d0 100644
--- a/ipawebui/engine.py
+++ b/ipawebui/engine.py
@@ -1,6 +1,6 @@
# Authors: Jason Gerard DeRose <jderose@redhat.com>
#
-# Copyright (C) 2008 Red Hat
+# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
@@ -21,6 +21,7 @@ Engine to map ipalib plugins to wehjit widgets.
"""
from controllers import Command
+from ipalib import crud
class ParamMapper(object):
def __init__(self, api, app):
@@ -28,7 +29,7 @@ class ParamMapper(object):
self._app = app
self.__methods = dict()
for name in dir(self):
- if name.startswith('_') or name.endswith('_'):
+ if name.startswith('_'):
continue
attr = getattr(self, name)
if not callable(attr):
@@ -40,13 +41,12 @@ class ParamMapper(object):
if key in self.__methods:
method = self.__methods[key]
else:
- #raise Warning('No ParamMapper for %r' % key)
method = self.Str
return method(param, cmd)
def Str(self, param, cmd):
return self._app.new('TextRow',
- label=param.cli_name,
+ label=param.label,
name=param.name,
required=param.required,
value=param.default,
@@ -61,7 +61,7 @@ class ParamMapper(object):
def Flag(self, param, cmd):
return self._app.new('SelectRow',
name=param.name,
- label=param.cli_name,
+ label=param.label,
)
@@ -128,23 +128,43 @@ class Engine(object):
return page
def build_page(self, cmd):
- page = self.app.new('PageApp',
+ page = self.app.new('PageCmd',
+ cmd=cmd,
id=cmd.name,
title=cmd.summary.rstrip('.'),
)
- #page.form.action = self.app.url + '__json__'
- page.actions.add(
- self.app.new('Submit')
+ page.form.action = page.url
+ page.form.method = 'GET'
+ page.form.add(
+ self.app.new('Hidden', name='__mode__', value='output')
)
- table = self.app.new('FieldTable')
- page.view.add(table)
- for param in cmd.params():
- if param.exclude and 'webui' in param.exclude:
- continue
- field = self.param_mapper(param, cmd)
- table.add(field)
+ page.notification = self.app.new('Notification')
+ page.view.add(page.notification)
+ page.prompt = self.make_prompt(cmd)
+ page.show = self.make_show(cmd)
+ self.conditional('input', page.actions, self.app.new('Submit'))
+ self.conditional('input', page.view, page.prompt)
+ self.conditional('output', page.view, page.show)
+ return page
+
+ def conditional(self, mode, parent, *children):
+ conditional = self.app.new('Conditional', mode=mode)
+ conditional.add(*children)
+ parent.add(conditional)
- page.form.action = '/'.join([self.jsonurl, cmd.name])
+ def make_prompt(self, cmd):
+ table = self.app.new('FieldTable')
+ for param in self._iter_params(cmd.params):
+ table.add(
+ self.param_mapper(param, cmd)
+ )
+ return table
+ def make_show(self, cmd):
+ return self.app.new('Output')
- return page
+ def _iter_params(self, namespace):
+ for param in namespace():
+ if param.exclude and 'webui' in param.exclude:
+ continue
+ yield param
diff --git a/ipawebui/widgets.py b/ipawebui/widgets.py
index 74b9d7e0c..1cf1adb97 100644
--- a/ipawebui/widgets.py
+++ b/ipawebui/widgets.py
@@ -1,6 +1,6 @@
# Authors: Jason Gerard DeRose <jderose@redhat.com>
#
-# Copyright (C) 2008 Red Hat
+# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
@@ -21,9 +21,10 @@ Custom IPA widgets.
"""
from textwrap import dedent
-from wehjit import Collection, base, freeze
+from wehjit import Collection, base, freeze, builtins
from wehjit.util import Alternator
from wehjit import Static, Dynamic, StaticProp, DynamicProp
+from ipaserver.rpcserver import extract_query
class IPAPlugins(base.Container):
@@ -189,6 +190,14 @@ class Command(base.Widget):
<td py:content="repr(option)" />
</tr>
+ <tr py:if="plugin.output" class="${row.next()}">
+ <th colspan="2" py:content="'output (%d)' % len(plugin.output)" />
+ </tr>
+ <tr py:for="param in plugin.output()" class="${row.next()}">
+ <td py:content="param.name"/>
+ <td py:content="repr(param)" />
+ </tr>
+
</table>
"""
@@ -211,7 +220,7 @@ class Object(base.Widget):
<th colspan="2" py:content="'params (%d)' % len(plugin.params)" />
</tr>
<tr py:for="param in plugin.params()" class="${row.next()}">
- <td py:content="param.name"/>
+ <td>${"param.name"}:</td>
<td py:content="repr(param)" />
</tr>
@@ -219,6 +228,171 @@ class Object(base.Widget):
"""
+
+class Conditional(base.Container):
+
+ mode = Static('mode', default='input')
+
+ @DynamicProp
+ def page_mode(self):
+ if self.page is None:
+ return
+ return self.page.mode
+
+ xml = """
+ <div
+ xmlns:py="http://genshi.edgewall.org/"
+ py:if="mode == page_mode"
+ py:strip="True"
+ >
+ <child py:for="child in children" py:replace="child.generate()" />
+ </div>
+ """
+
+
+class Output(base.Widget):
+ """
+ Shows attributes form an LDAP entry.
+ """
+
+ order = Dynamic('order')
+ labels = Dynamic('labels')
+ result = Dynamic('result')
+
+ xml = """
+ <div
+ xmlns:py="http://genshi.edgewall.org/"
+ class="${klass}"
+ id="${id}"
+ >
+ <table py:if="isinstance(result, dict)">
+ <tr py:for="key in order" py:if="key in result">
+ <th py:content="labels[key]" />
+ <td py:content="result[key]" />
+ </tr>
+ </table>
+
+ <table
+ py:if="isinstance(result, (list, tuple)) and len(result) > 0"
+ >
+ <tr>
+ <th
+ py:for="key in order"
+ py:if="key in result[0]"
+ py:content="labels[key]"
+ />
+ </tr>
+ <tr py:for="entry in result">
+ <td
+ py:for="key in order"
+ py:if="key in result[0]"
+ py:content="entry[key]"
+ />
+ </tr>
+ </table>
+ </div>
+ """
+
+ style = (
+ ('table', (
+ ('empty-cells', 'show'),
+ ('border-collapse', 'collapse'),
+ )),
+
+ ('th', (
+ ('text-align', 'right'),
+ ('padding', '.25em 0.5em'),
+ ('line-height', '%(height_bar)s'),
+ ('vertical-align', 'top'),
+ )),
+
+ ('td', (
+ ('padding', '.25em'),
+ ('vertical-align', 'top'),
+ ('text-align', 'left'),
+ ('line-height', '%(height_bar)s'),
+ )),
+ )
+
+
+class Hidden(base.Field):
+ xml = """
+ <input
+ xmlns:py="http://genshi.edgewall.org/"
+ type="hidden"
+ name="${name}"
+ />
+ """
+
+
+class Notification(base.Widget):
+ message = Dynamic('message')
+ error = Dynamic('error', default=False)
+
+ @property
+ def extra_css_classes(self):
+ if self.error:
+ yield 'error'
+ else:
+ yield 'okay'
+
+ xml = """
+ <p
+ xmlns:py="http://genshi.edgewall.org/"
+ class="${klass}"
+ id="${id}"
+ py:if="message"
+ py:content="message"
+ />
+ """
+
+ style = (
+ ('', (
+ ('font-weight', 'bold'),
+ ('-moz-border-radius', '100%%'),
+ ('background-color', '#eee'),
+ ('border', '2px solid #966'),
+ ('padding', '0.5em'),
+ ('text-align', 'center'),
+ )),
+ )
+
+
+class PageCmd(builtins.PageApp):
+ cmd = Static('cmd')
+ mode = Dynamic('mode', default='input')
+
+ def controller(self, environ):
+ query = extract_query(environ)
+ self.mode = query.pop('__mode__', 'input')
+ if self.mode == 'input':
+ return
+ soft = self.cmd.soft_validate(query)
+ errors = soft['errors']
+ values = soft['values']
+ if errors:
+ self.mode = 'input'
+ for key in self.form:
+ if key in errors:
+ self.form[key].error = errors[key]
+ if key in values:
+ self.form[key].value = values[key]
+ return
+ output = self.cmd(**query)
+ if isinstance(output, dict) and 'summary' in output:
+ self.notification.message = output['summary']
+ params = self.cmd.output_params
+ if params:
+ order = list(params)
+ labels = dict((p.name, p.label) for p in params())
+ else:
+ order = sorted(entry)
+ labels = dict((k, k) for k in order)
+ self.show.order = order
+ self.show.labels = labels
+ self.show.result = output.get('result')
+
+
def create_widgets():
widgets = Collection('freeIPA')
widgets.register_builtins()
@@ -227,6 +401,12 @@ def create_widgets():
widgets.register(IPAPlugins)
widgets.register(Command)
widgets.register(Object)
+ widgets.register(Conditional)
+ widgets.register(Output)
+ widgets.register(Hidden)
+ widgets.register(Notification)
+
+ widgets.register(PageCmd)
freeze(widgets)