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)
|
value = getattr(options, key, None)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
overrides[key] = value
|
overrides[key] = value
|
||||||
|
if hasattr(options, 'prod'):
|
||||||
|
overrides['webui_prod'] = options.prod
|
||||||
if context is not None:
|
if context is not None:
|
||||||
overrides['context'] = context
|
overrides['context'] = context
|
||||||
self.bootstrap(**overrides)
|
self.bootstrap(**overrides)
|
||||||
|
@ -133,6 +133,7 @@ class LDAPCreate(crud.Create):
|
|||||||
"""
|
"""
|
||||||
Create a new entry in LDAP.
|
Create a new entry in LDAP.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
takes_options = (
|
takes_options = (
|
||||||
Flag('raw',
|
Flag('raw',
|
||||||
cli_name='raw',
|
cli_name='raw',
|
||||||
@ -142,6 +143,7 @@ class LDAPCreate(crud.Create):
|
|||||||
Flag('all',
|
Flag('all',
|
||||||
cli_name='all',
|
cli_name='all',
|
||||||
doc='retrieve all attributes',
|
doc='retrieve all attributes',
|
||||||
|
exclude='webui',
|
||||||
),
|
),
|
||||||
Str('addattr*', validate_add_attribute,
|
Str('addattr*', validate_add_attribute,
|
||||||
cli_name='addattr',
|
cli_name='addattr',
|
||||||
@ -291,14 +293,17 @@ class LDAPUpdate(LDAPQuery, crud.Update):
|
|||||||
"""
|
"""
|
||||||
Update an LDAP entry.
|
Update an LDAP entry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
takes_options = (
|
takes_options = (
|
||||||
Flag('raw',
|
Flag('raw',
|
||||||
cli_name='raw',
|
cli_name='raw',
|
||||||
doc='print entries as they are stored in LDAP',
|
doc='print entries as they are stored in LDAP',
|
||||||
|
exclude='webui',
|
||||||
),
|
),
|
||||||
Flag('all',
|
Flag('all',
|
||||||
cli_name='all',
|
cli_name='all',
|
||||||
doc='retrieve all attributes',
|
doc='retrieve all attributes',
|
||||||
|
exclude='webui',
|
||||||
),
|
),
|
||||||
Str('addattr*', validate_add_attribute,
|
Str('addattr*', validate_add_attribute,
|
||||||
cli_name='addattr',
|
cli_name='addattr',
|
||||||
@ -456,6 +461,7 @@ class LDAPModMember(LDAPQuery):
|
|||||||
Flag('raw',
|
Flag('raw',
|
||||||
cli_name='raw',
|
cli_name='raw',
|
||||||
doc='print entries as they are stored in LDAP',
|
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):
|
def post_callback(self, ldap, entries, truncated, *args, **options):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -113,6 +113,9 @@ class user(LDAPObject):
|
|||||||
cli_name='password',
|
cli_name='password',
|
||||||
label='Password',
|
label='Password',
|
||||||
doc='Set the user 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?',
|
Int('uidnumber?',
|
||||||
cli_name='uid',
|
cli_name='uid',
|
||||||
|
@ -273,4 +273,5 @@ class jsonserver(WSGIExecutioner):
|
|||||||
raise JSONError(
|
raise JSONError(
|
||||||
error='params[1] (aka options) must be a dict'
|
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)
|
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):
|
class Engine(object):
|
||||||
|
|
||||||
|
cruds = frozenset(['add', 'show', 'mod', 'del', 'find'])
|
||||||
|
|
||||||
def __init__(self, api, app):
|
def __init__(self, api, app):
|
||||||
self.api = api
|
self.api = api
|
||||||
self.app = app
|
self.app = app
|
||||||
@ -86,11 +96,21 @@ class Engine(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
for cmd in self.api.Object.user.methods():
|
for obj in self.api.Object():
|
||||||
self.pages[cmd.name] = self.build_page(cmd)
|
if self.cruds.issubset(obj.methods) and obj.primary_key is not None:
|
||||||
for page in self.pages.itervalues():
|
self.pages[obj.name] = self.build_cruds_page(obj)
|
||||||
page.menu.label = 'Users'
|
|
||||||
self.add_object_menuitems(page.menu, 'user')
|
# 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:
|
# Add in the info pages:
|
||||||
page = self.app.new('PageApp', id='api', title='api')
|
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):
|
def build_info_page(self, kind):
|
||||||
# Add in the Object page:
|
# Add in the Object page:
|
||||||
plugins = tuple(self.api[kind]())
|
plugins = tuple(self.api[kind]())
|
||||||
@ -126,45 +198,3 @@ class Engine(object):
|
|||||||
self.app.new(kind)
|
self.app.new(kind)
|
||||||
)
|
)
|
||||||
return page
|
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 LandingPage(base.Widget):
|
||||||
class Conditional(base.Container):
|
pages = Static('pages', default=tuple())
|
||||||
|
|
||||||
mode = Static('mode', default='input')
|
|
||||||
|
|
||||||
@DynamicProp
|
|
||||||
def page_mode(self):
|
|
||||||
if self.page is None:
|
|
||||||
return
|
|
||||||
return self.page.mode
|
|
||||||
|
|
||||||
xml = """
|
xml = """
|
||||||
<div
|
<div
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
py:if="mode == page_mode"
|
class="${css_classes}"
|
||||||
py:strip="True"
|
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>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Output(base.Widget):
|
class Form(builtins.Form):
|
||||||
"""
|
js_class = 'Form'
|
||||||
Shows attributes form an LDAP entry.
|
|
||||||
"""
|
|
||||||
|
|
||||||
order = Dynamic('order')
|
javascript = """
|
||||||
labels = Dynamic('labels')
|
Wehjit.bases.Form = new Class({
|
||||||
result = Dynamic('result')
|
Extends: Wehjit.bases.Widget,
|
||||||
|
|
||||||
xml = """
|
post_init: function() {
|
||||||
<div
|
this.focused = null;
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
$each(this.el.elements, function(field) {
|
||||||
class="${klass}"
|
field.connect('focus', this);
|
||||||
id="${id}"
|
}, this);
|
||||||
>
|
var parent = this.get_parent();
|
||||||
<table py:if="isinstance(result, dict)">
|
if (parent && parent.klass == 'Dialog') {
|
||||||
<tr py:for="key in order" py:if="key in result">
|
parent.addEvent('run', this.on_run.bind(this));
|
||||||
<th py:content="labels[key]" />
|
this.parent = parent;
|
||||||
<td py:content="result[key]" />
|
}
|
||||||
</tr>
|
this.formdata = null;
|
||||||
</table>
|
},
|
||||||
|
|
||||||
<table
|
on_focus: function(field, event) {
|
||||||
py:if="isinstance(result, (list, tuple)) and len(result) > 0"
|
this.focused = field;
|
||||||
>
|
},
|
||||||
<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 = (
|
on_run: function(dialog, params) {
|
||||||
('table', (
|
console.assert(dialog == this.parent);
|
||||||
('empty-cells', 'show'),
|
this.refocus();
|
||||||
('border-collapse', 'collapse'),
|
},
|
||||||
)),
|
|
||||||
|
|
||||||
('th', (
|
refocus: function() {
|
||||||
('text-align', 'right'),
|
console.log('refocus', this.id, this.focused);
|
||||||
('padding', '.25em 0.5em'),
|
if (this.focused) {
|
||||||
('line-height', '%(height_bar)s'),
|
this.focused.focus();
|
||||||
('vertical-align', 'top'),
|
return true;
|
||||||
)),
|
}
|
||||||
|
if (this.el.elements.length > 0) {
|
||||||
|
this.el.elements[0].focus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
('td', (
|
get_data: function() {
|
||||||
('padding', '.25em'),
|
console.log('Form.get_data');
|
||||||
('vertical-align', 'top'),
|
var rawdata = this.el.get_data();
|
||||||
('text-align', 'left'),
|
var data = {};
|
||||||
('line-height', '%(height_bar)s'),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
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):
|
return data;
|
||||||
xml = """
|
|
||||||
<input
|
},
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
|
||||||
type="hidden"
|
set_data: function(data) {
|
||||||
name="${name}"
|
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):
|
class CRUDS(builtins.CRUDS):
|
||||||
message = Dynamic('message')
|
display_cols = Static('display_cols', json=True, default=tuple())
|
||||||
error = Dynamic('error', default=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_css_classes(self):
|
|
||||||
if self.error:
|
|
||||||
yield 'error'
|
|
||||||
else:
|
|
||||||
yield 'okay'
|
|
||||||
|
|
||||||
xml = """
|
class Display(builtins.Display):
|
||||||
<p
|
cols = None
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
|
||||||
class="${klass}"
|
javascript = """
|
||||||
id="${id}"
|
Wehjit.bases.Display = new Class({
|
||||||
py:if="message"
|
Extends: Wehjit.bases.Widget,
|
||||||
py:content="message"
|
|
||||||
/>
|
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():
|
def create_widgets():
|
||||||
@ -401,12 +402,10 @@ def create_widgets():
|
|||||||
widgets.register(IPAPlugins)
|
widgets.register(IPAPlugins)
|
||||||
widgets.register(Command)
|
widgets.register(Command)
|
||||||
widgets.register(Object)
|
widgets.register(Object)
|
||||||
widgets.register(Conditional)
|
widgets.register(LandingPage)
|
||||||
widgets.register(Output)
|
widgets.register(Form, override=True)
|
||||||
widgets.register(Hidden)
|
widgets.register(CRUDS, override=True)
|
||||||
widgets.register(Notification)
|
widgets.register(Display, override=True)
|
||||||
|
|
||||||
widgets.register(PageCmd)
|
|
||||||
|
|
||||||
|
|
||||||
freeze(widgets)
|
freeze(widgets)
|
||||||
|
Loading…
Reference in New Issue
Block a user