mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Enabled CRUDS in webUI using wehjit 0.2.0
This commit is contained in:
parent
e9dc9de23a
commit
7b571e3693
@ -531,6 +531,8 @@ class API(DictProxy):
|
||||
value = getattr(options, key, None)
|
||||
if value is not None:
|
||||
overrides[key] = value
|
||||
if hasattr(options, 'prod'):
|
||||
overrides['webui_prod'] = options.prod
|
||||
if context is not None:
|
||||
overrides['context'] = context
|
||||
self.bootstrap(**overrides)
|
||||
|
@ -133,6 +133,7 @@ class LDAPCreate(crud.Create):
|
||||
"""
|
||||
Create a new entry in LDAP.
|
||||
"""
|
||||
|
||||
takes_options = (
|
||||
Flag('raw',
|
||||
cli_name='raw',
|
||||
@ -142,6 +143,7 @@ class LDAPCreate(crud.Create):
|
||||
Flag('all',
|
||||
cli_name='all',
|
||||
doc='retrieve all attributes',
|
||||
exclude='webui',
|
||||
),
|
||||
Str('addattr*', validate_add_attribute,
|
||||
cli_name='addattr',
|
||||
@ -291,14 +293,17 @@ class LDAPUpdate(LDAPQuery, crud.Update):
|
||||
"""
|
||||
Update an LDAP entry.
|
||||
"""
|
||||
|
||||
takes_options = (
|
||||
Flag('raw',
|
||||
cli_name='raw',
|
||||
doc='print entries as they are stored in LDAP',
|
||||
exclude='webui',
|
||||
),
|
||||
Flag('all',
|
||||
cli_name='all',
|
||||
doc='retrieve all attributes',
|
||||
exclude='webui',
|
||||
),
|
||||
Str('addattr*', validate_add_attribute,
|
||||
cli_name='addattr',
|
||||
@ -456,6 +461,7 @@ class LDAPModMember(LDAPQuery):
|
||||
Flag('raw',
|
||||
cli_name='raw',
|
||||
doc='print entries as they are stored in LDAP',
|
||||
exclude='webui',
|
||||
),
|
||||
)
|
||||
|
||||
@ -751,4 +757,3 @@ class LDAPSearch(crud.Search):
|
||||
|
||||
def post_callback(self, ldap, entries, truncated, *args, **options):
|
||||
pass
|
||||
|
||||
|
@ -113,6 +113,9 @@ class user(LDAPObject):
|
||||
cli_name='password',
|
||||
label='Password',
|
||||
doc='Set the user password',
|
||||
# FIXME: This is temporary till bug is fixed causing updates to
|
||||
# bomb out via the webUI.
|
||||
exclude='webui',
|
||||
),
|
||||
Int('uidnumber?',
|
||||
cli_name='uid',
|
||||
|
@ -273,4 +273,5 @@ class jsonserver(WSGIExecutioner):
|
||||
raise JSONError(
|
||||
error='params[1] (aka options) must be a dict'
|
||||
)
|
||||
options = dict((str(k), v) for (k, v) in options.iteritems())
|
||||
return (method, args, options, _id)
|
||||
|
@ -65,7 +65,17 @@ class ParamMapper(object):
|
||||
)
|
||||
|
||||
|
||||
def filter_params(namespace):
|
||||
for param in namespace():
|
||||
if param.exclude and 'webui' in param.exclude:
|
||||
continue
|
||||
yield param
|
||||
|
||||
|
||||
class Engine(object):
|
||||
|
||||
cruds = frozenset(['add', 'show', 'mod', 'del', 'find'])
|
||||
|
||||
def __init__(self, api, app):
|
||||
self.api = api
|
||||
self.app = app
|
||||
@ -86,11 +96,21 @@ class Engine(object):
|
||||
)
|
||||
|
||||
def build(self):
|
||||
for cmd in self.api.Object.user.methods():
|
||||
self.pages[cmd.name] = self.build_page(cmd)
|
||||
for page in self.pages.itervalues():
|
||||
page.menu.label = 'Users'
|
||||
self.add_object_menuitems(page.menu, 'user')
|
||||
for obj in self.api.Object():
|
||||
if self.cruds.issubset(obj.methods) and obj.primary_key is not None:
|
||||
self.pages[obj.name] = self.build_cruds_page(obj)
|
||||
|
||||
# Add landing page:
|
||||
landing = self.app.new('PageApp', id='', title='Welcome to FreeIPA')
|
||||
|
||||
for page in self.pages.values() + [landing]:
|
||||
page.menu.label = 'FreeIPA'
|
||||
for name in sorted(self.pages):
|
||||
p = self.pages[name]
|
||||
page.menu.new_child('MenuItem', label=p.title, href=p.url)
|
||||
|
||||
|
||||
|
||||
|
||||
# Add in the info pages:
|
||||
page = self.app.new('PageApp', id='api', title='api')
|
||||
@ -110,6 +130,58 @@ class Engine(object):
|
||||
)
|
||||
)
|
||||
|
||||
def build_cruds_page(self, obj):
|
||||
page = self.app.new('PageGrid', title=obj.name, id=obj.name)
|
||||
|
||||
# Setup CRUDS widget:
|
||||
page.cruds.autoload = True
|
||||
page.cruds.jsonrpc_url = self.api.Backend.jsonserver.url
|
||||
page.cruds.key = obj.primary_key.name
|
||||
page.cruds.method_create = obj.methods['add'].name
|
||||
page.cruds.method_retrieve = obj.methods['show'].name
|
||||
page.cruds.method_update = obj.methods['mod'].name
|
||||
page.cruds.method_delete = obj.methods['del'].name
|
||||
page.cruds.method_search = obj.methods['find'].name
|
||||
page.cruds.display_cols = tuple(
|
||||
dict(
|
||||
name=p.name,
|
||||
label=p.label,
|
||||
css_classes=None,
|
||||
)
|
||||
for p in obj.params()
|
||||
)
|
||||
|
||||
# Setup the Grid widget:
|
||||
page.grid.cols = tuple(
|
||||
dict(
|
||||
name=p.name,
|
||||
label=p.label,
|
||||
css_classes=None,
|
||||
)
|
||||
for p in obj.params() if p.required
|
||||
)
|
||||
|
||||
|
||||
# Setup the create Dialog:
|
||||
cmd = obj.methods['add']
|
||||
page.create.title = cmd.summary.rstrip('.')
|
||||
for p in filter_params(cmd.params):
|
||||
page.create.fieldtable.add(self.param_mapper(p, cmd))
|
||||
|
||||
# Setup the retrieve Dialog
|
||||
page.retrieve.title = 'Showing "{value}"'
|
||||
|
||||
# Setup the update Dialog:
|
||||
page.update.title = 'Updating "{value}"'
|
||||
cmd = obj.methods['mod']
|
||||
for p in filter_params(cmd.options):
|
||||
page.update.fieldtable.add(self.param_mapper(p, cmd))
|
||||
|
||||
# Setup the delete Dialog
|
||||
page.delete.title = 'Delete "{value}"?'
|
||||
|
||||
return page
|
||||
|
||||
def build_info_page(self, kind):
|
||||
# Add in the Object page:
|
||||
plugins = tuple(self.api[kind]())
|
||||
@ -126,45 +198,3 @@ class Engine(object):
|
||||
self.app.new(kind)
|
||||
)
|
||||
return page
|
||||
|
||||
def build_page(self, cmd):
|
||||
page = self.app.new('PageCmd',
|
||||
cmd=cmd,
|
||||
id=cmd.name,
|
||||
title=cmd.summary.rstrip('.'),
|
||||
)
|
||||
page.form.action = page.url
|
||||
page.form.method = 'GET'
|
||||
page.form.add(
|
||||
self.app.new('Hidden', name='__mode__', value='output')
|
||||
)
|
||||
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)
|
||||
|
||||
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')
|
||||
|
||||
def _iter_params(self, namespace):
|
||||
for param in namespace():
|
||||
if param.exclude and 'webui' in param.exclude:
|
||||
continue
|
||||
yield param
|
||||
|
@ -228,169 +228,170 @@ 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
|
||||
class LandingPage(base.Widget):
|
||||
pages = Static('pages', default=tuple())
|
||||
|
||||
xml = """
|
||||
<div
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
py:if="mode == page_mode"
|
||||
py:strip="True"
|
||||
class="${css_classes}"
|
||||
id="${id}"
|
||||
>
|
||||
<child py:for="child in children" py:replace="child.generate()" />
|
||||
<a
|
||||
py:for="p in pages"
|
||||
py:content="p.title"
|
||||
href="${relurl(p.url)}"
|
||||
/>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
class Output(base.Widget):
|
||||
"""
|
||||
Shows attributes form an LDAP entry.
|
||||
"""
|
||||
class Form(builtins.Form):
|
||||
js_class = 'Form'
|
||||
|
||||
order = Dynamic('order')
|
||||
labels = Dynamic('labels')
|
||||
result = Dynamic('result')
|
||||
javascript = """
|
||||
Wehjit.bases.Form = new Class({
|
||||
Extends: Wehjit.bases.Widget,
|
||||
|
||||
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>
|
||||
post_init: function() {
|
||||
this.focused = null;
|
||||
$each(this.el.elements, function(field) {
|
||||
field.connect('focus', this);
|
||||
}, this);
|
||||
var parent = this.get_parent();
|
||||
if (parent && parent.klass == 'Dialog') {
|
||||
parent.addEvent('run', this.on_run.bind(this));
|
||||
this.parent = parent;
|
||||
}
|
||||
this.formdata = null;
|
||||
},
|
||||
|
||||
<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>
|
||||
"""
|
||||
on_focus: function(field, event) {
|
||||
this.focused = field;
|
||||
},
|
||||
|
||||
style = (
|
||||
('table', (
|
||||
('empty-cells', 'show'),
|
||||
('border-collapse', 'collapse'),
|
||||
)),
|
||||
on_run: function(dialog, params) {
|
||||
console.assert(dialog == this.parent);
|
||||
this.refocus();
|
||||
},
|
||||
|
||||
('th', (
|
||||
('text-align', 'right'),
|
||||
('padding', '.25em 0.5em'),
|
||||
('line-height', '%(height_bar)s'),
|
||||
('vertical-align', 'top'),
|
||||
)),
|
||||
refocus: function() {
|
||||
console.log('refocus', this.id, this.focused);
|
||||
if (this.focused) {
|
||||
this.focused.focus();
|
||||
return true;
|
||||
}
|
||||
if (this.el.elements.length > 0) {
|
||||
this.el.elements[0].focus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
('td', (
|
||||
('padding', '.25em'),
|
||||
('vertical-align', 'top'),
|
||||
('text-align', 'left'),
|
||||
('line-height', '%(height_bar)s'),
|
||||
)),
|
||||
)
|
||||
get_data: function() {
|
||||
console.log('Form.get_data');
|
||||
var rawdata = this.el.get_data();
|
||||
var data = {};
|
||||
|
||||
if (this.formdata == null) {
|
||||
$each(rawdata, function(value, key) {
|
||||
if (value !== '') {
|
||||
data[key] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
$each(rawdata, function(value, key) {
|
||||
var old = this.formdata[key];
|
||||
if (old == undefined && value === '') {
|
||||
return;
|
||||
}
|
||||
if (old != value) {
|
||||
console.log('changed: %s = %s', key, value);
|
||||
data[key] = value;
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
class Hidden(base.Field):
|
||||
xml = """
|
||||
<input
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
type="hidden"
|
||||
name="${name}"
|
||||
/>
|
||||
return data;
|
||||
|
||||
},
|
||||
|
||||
set_data: function(data) {
|
||||
console.log('Form.set_data', data);
|
||||
this.focused = null;
|
||||
if ($type(data) == 'object') {
|
||||
this.formdata = data;
|
||||
}
|
||||
else {
|
||||
this.formdata = null;
|
||||
}
|
||||
this.el.set_data(data);
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.formdata = null;
|
||||
this.focused = null;
|
||||
this.el.reset();
|
||||
},
|
||||
|
||||
});
|
||||
"""
|
||||
|
||||
|
||||
class Notification(base.Widget):
|
||||
message = Dynamic('message')
|
||||
error = Dynamic('error', default=False)
|
||||
class CRUDS(builtins.CRUDS):
|
||||
display_cols = Static('display_cols', json=True, default=tuple())
|
||||
|
||||
@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"
|
||||
/>
|
||||
class Display(builtins.Display):
|
||||
cols = None
|
||||
|
||||
javascript = """
|
||||
Wehjit.bases.Display = new Class({
|
||||
Extends: Wehjit.bases.Widget,
|
||||
|
||||
post_init: function() {
|
||||
var parent = this.get_parent();
|
||||
console.assert(parent);
|
||||
parent.addEvent('run', this.on_run.bind(this));
|
||||
this.cruds = Wehjit.get('cruds');
|
||||
this.cols = this.cruds.data.display_cols;
|
||||
console.assert(this.cols);
|
||||
if (this.cols.length == 0) {
|
||||
this.cols = Wehjit.data.grid.cols;
|
||||
}
|
||||
},
|
||||
|
||||
on_run: function(dialog, row) {
|
||||
console.log('Display.on_run(%s, %s)', dialog, row);
|
||||
this.el.empty();
|
||||
if ($type(row) != 'object') {
|
||||
return;
|
||||
}
|
||||
this.cols.each(function(col) {
|
||||
var tr = new Element('tr');
|
||||
var th = new Element('th');
|
||||
th.textContent = col.label + ':';
|
||||
tr.appendChild(th);
|
||||
this.el.appendChild(tr);
|
||||
var td = new Element('td');
|
||||
var value = row[col.name];
|
||||
if ($type(value) == 'array') {
|
||||
var value = value.join(',');
|
||||
}
|
||||
if ($type(value) != 'string') {
|
||||
var value = '';
|
||||
}
|
||||
td.textContent = value;
|
||||
tr.appendChild(td);
|
||||
}, this);
|
||||
},
|
||||
|
||||
});
|
||||
"""
|
||||
|
||||
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():
|
||||
@ -401,12 +402,10 @@ 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)
|
||||
widgets.register(LandingPage)
|
||||
widgets.register(Form, override=True)
|
||||
widgets.register(CRUDS, override=True)
|
||||
widgets.register(Display, override=True)
|
||||
|
||||
|
||||
freeze(widgets)
|
||||
|
Loading…
Reference in New Issue
Block a user