diff --git a/install/ui/Makefile.am b/install/ui/Makefile.am index ea3a20295..7eb9b04ce 100644 --- a/install/ui/Makefile.am +++ b/install/ui/Makefile.am @@ -61,6 +61,7 @@ app_DATA = \ serverconfig.js \ service.js \ sudo.js \ + trust.js \ user.js \ webui.js \ widget.js \ diff --git a/install/ui/dialog.js b/install/ui/dialog.js index 9003aa820..2af9ee332 100644 --- a/install/ui/dialog.js +++ b/install/ui/dialog.js @@ -232,13 +232,15 @@ IPA.dialog = function(spec) { var widget_builder = IPA.widget_builder({ widget_options: { - entity: that.entity + entity: that.entity, + facet: that } }); var field_builder = IPA.field_builder({ field_options: { undo: false, - entity: that.entity + entity: that.entity, + facet: that } }); var section_builder = IPA.section_builder({ diff --git a/install/ui/index.html b/install/ui/index.html index 01335d755..653704b7b 100644 --- a/install/ui/index.html +++ b/install/ui/index.html @@ -43,6 +43,7 @@ + diff --git a/install/ui/ipa.css b/install/ui/ipa.css index 2ce8494ab..c0cec89a4 100644 --- a/install/ui/ipa.css +++ b/install/ui/ipa.css @@ -1756,4 +1756,15 @@ form#login { .disabled { color: gray; cursor: default; +} + +/* --- Multiple choice widget --- */ + +.multiple-choice-section-header { + font-weight: bold; + font-size: 1.1em; +} + +.choice-header { + font-weight: bold; } \ No newline at end of file diff --git a/install/ui/ipa.js b/install/ui/ipa.js index 7667c7669..f0ad01c32 100644 --- a/install/ui/ipa.js +++ b/install/ui/ipa.js @@ -1087,6 +1087,34 @@ IPA.build = function(spec, builder_fac) { return product; }; +IPA.build_default = function(spec, def_spec) { + + var builder, factory, default_object; + + if (!spec && !def_spec) return null; + + if (typeof def_spec === 'function') { //factory function + factory = def_spec; + } else if (typeof def_spec === 'object') { + default_object = def_spec; + } + + builder = IPA.builder({ + factory: factory + }); + + var product; + spec = spec || default_object || {}; + + if ($.isArray(spec)) { + product = builder.build_objects(spec); + } else { + product = builder.build(spec); + } + + return product; +}; + IPA.default_factory = function(spec) { spec = spec || {}; diff --git a/install/ui/jquery.ordered-map.js b/install/ui/jquery.ordered-map.js index 64cad6e03..33737b564 100755 --- a/install/ui/jquery.ordered-map.js +++ b/install/ui/jquery.ordered-map.js @@ -47,11 +47,13 @@ jQuery.ordered_map = jQuery.fn.ordered_map = function(map) { } that.map[key] = value; + + return that; }; that.put_map = function(map) { - if (typeof map !== 'object') return; + if (typeof map !== 'object') return that; for (name in map) { @@ -59,6 +61,35 @@ jQuery.ordered_map = jQuery.fn.ordered_map = function(map) { that.put(name, map[name]); } } + + return that; + }; + + that.put_array = function(array, key_name, operation) { + + var i, item, type, key; + + array = array || []; + + for (i=0; i", + "flags": [], + "label": "", + "multivalue": true, + "name": "usercertificate", + "type": "unicode" + } + ], + "takes_options": [ + { + "name": "setattr" + }, + { + "name": "addattr" + }, + { + "class": "Str", + "default": "IMPORTED", + "doc": "Enrollment UUID", + "flags": [ + "no_update", + "no_create" + ], + "label": "UUID", + "name": "uuid", + "noextrawhitespace": true, + "type": "unicode" + } + ] + }, + "entitle_register": { + "takes_args": [ + { + "class": "Str", + "doc": "Username", + "flags": [], + "label": "Username", + "name": "username", + "noextrawhitespace": true, + "required": true, + "type": "unicode" + } + ], + "takes_options": [ + { + "name": "setattr" + }, + { + "name": "addattr" + }, + { + "class": "Str", + "doc": "Enrollment UUID (not implemented)", + "flags": [ + "no_update", + "no_create" + ], + "label": "UUID", + "name": "ipaentitlementid", + "noextrawhitespace": true, + "type": "unicode" + }, + { + "class": "Password", + "doc": "Registration password", + "flags": [], + "label": "Password", + "name": "password", + "noextrawhitespace": true, + "required": true, + "type": "unicode" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + } + ] + }, + "entitle_status": { + "name": "entitle_status", + "takes_args": [], + "takes_options": [] + }, + "entitle_sync": { + "takes_args": [], + "takes_options": [ + { + "class": "Int", + "default": 1, + "doc": "Quantity", + "flags": [ + "no_option", + "no_output" + ], + "label": "Quantity", + "maxvalue": 2147483647, + "minvalue": 1, + "name": "hidden", + "required": true, + "type": "int" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + } + ] + }, "env": { "name": "env", "takes_args": [ @@ -15784,6 +15999,194 @@ } ] }, + "trust_add": { + "takes_args": [], + "takes_options": [ + { + "class": "StrEnum", + "default": "ad", + "doc": "Trust type (ad for Active Directory, default)", + "flags": [], + "label": "Trust type (ad for Active Directory, default)", + "name": "trust_type", + "required": true, + "type": "unicode", + "values": [ + "ad" + ] + }, + { + "class": "Str", + "doc": "Active Directory domain administrator", + "flags": [], + "label": "Active Directory domain administrator", + "name": "realm_admin", + "noextrawhitespace": true, + "type": "unicode" + }, + { + "class": "Password", + "doc": "Active directory domain adminstrator's password", + "flags": [], + "label": "Active directory domain adminstrator's password", + "name": "realm_passwd", + "noextrawhitespace": true, + "type": "unicode" + }, + { + "class": "Str", + "doc": "Domain controller for the Active Directory domain (optional)", + "flags": [], + "label": "Domain controller for the Active Directory domain (optional)", + "name": "realm_server", + "noextrawhitespace": true, + "type": "unicode" + }, + { + "class": "Password", + "doc": "Shared secret for the trust", + "flags": [], + "label": "Shared secret for the trust", + "name": "trust_secret", + "noextrawhitespace": true, + "type": "unicode" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + } + ] + }, + "trust_del": { + "takes_args": [], + "takes_options": [ + { + "class": "Flag", + "doc": "Continuous mode: Don't stop on errors.", + "flags": [], + "label": "", + "name": "continue", + "required": true, + "type": "bool" + } + ] + }, + "trust_find": { + "takes_args": [], + "takes_options": [ + { + "attribute": true, + "class": "Str", + "doc": "Realm name", + "flags": [], + "label": "Realm name", + "name": "cn", + "noextrawhitespace": true, + "primary_key": true, + "query": true, + "type": "unicode" + }, + { + "class": "Int", + "doc": "Time limit of search in seconds", + "flags": [ + "no_display" + ], + "label": "Time Limit", + "maxvalue": 2147483647, + "name": "timelimit", + "type": "int" + }, + { + "class": "Int", + "doc": "Maximum number of entries returned", + "flags": [ + "no_display" + ], + "label": "Size Limit", + "maxvalue": 2147483647, + "name": "sizelimit", + "type": "int" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + }, + { + "class": "Flag", + "doc": "Results should contain primary key attribute only (\"realm\")", + "flags": [], + "label": "Primary key only", + "name": "pkey_only", + "type": "bool" + } + ] + }, + "trust_mod": { + "takes_args": [], + "takes_options": [ + { + "name": "setattr" + }, + { + "name": "addattr" + }, + { + "name": "delattr" + }, + { + "class": "Flag", + "doc": "Display the access rights of this entry (requires --all). See ipa man page for details.", + "flags": [], + "label": "Rights", + "name": "rights", + "required": true, + "type": "bool" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + } + ] + }, + "trust_show": { + "takes_args": [], + "takes_options": [ + { + "class": "Flag", + "doc": "Display the access rights of this entry (requires --all). See ipa man page for details.", + "flags": [], + "label": "Rights", + "name": "rights", + "required": true, + "type": "bool" + }, + { + "name": "all" + }, + { + "name": "raw" + }, + { + "name": "version" + } + ] + }, "user_add": { "takes_args": [], "takes_options": [ diff --git a/install/ui/test/data/ipa_init_objects.json b/install/ui/test/data/ipa_init_objects.json index c4adfd743..25db686ce 100644 --- a/install/ui/test/data/ipa_init_objects.json +++ b/install/ui/test/data/ipa_init_objects.json @@ -580,7 +580,9 @@ "type": "unicode", "values": [ "AllowLMhash", - "AllowNThash" + "AllowNThash", + "KDC:Disable Last Success", + "KDC:Disable Lockout" ] }, { @@ -817,8 +819,28 @@ "ipagroupobjectclasses", "ipagroupsearchfields", "ipahomesrootdir", + "ipakrbprincipalalias", "ipamaxusernamelength", "ipamigrationenabled", + "ipantdomainguid", + "ipantfallbackprimarygroup", + "ipantflatname", + "ipanthash", + "ipanthomedirectory", + "ipanthomedirectorydrive", + "ipantlogonscript", + "ipantprofilepath", + "ipantsecurityidentifier", + "ipantsupportedencryptiontypes", + "ipanttrustattributes", + "ipanttrustauthincoming", + "ipanttrustauthoutgoing", + "ipanttrustdirection", + "ipanttrusteddomainsid", + "ipanttrustforesttrustinfo", + "ipanttrustpartner", + "ipanttrustposixoffset", + "ipanttrusttype", "ipapermissiontype", "ipapwdexpadvnotify", "ipasearchrecordslimit", @@ -3915,6 +3937,67 @@ ], "uuid_attribute": "" }, + "entitle": { + "aciattrs": [ + "ipaentitlementid", + "ipauniqueid", + "usercertificate", + "userpkcs12" + ], + "attribute_members": {}, + "bindable": false, + "container_dn": "cn=entitlements,cn=etc", + "default_attributes": [ + "ipaentitlement" + ], + "hidden_attributes": [ + "objectclass", + "aci" + ], + "label": "Entitlements", + "label_singular": "Entitlement", + "methods": [ + "consume", + "find", + "import", + "register", + "sync" + ], + "name": "entitle", + "object_class": [ + "ipaobject", + "ipaentitlement" + ], + "object_class_config": null, + "object_name": "entitlement", + "object_name_plural": "entitlements", + "parent_object": "", + "rdn_attribute": "", + "relationships": { + "member": [ + "Member", + "", + "no_" + ], + "memberindirect": [ + "Indirect Member", + null, + "no_indirect_" + ], + "memberof": [ + "Member Of", + "in_", + "not_in_" + ], + "memberofindirect": [ + "Indirect Member Of", + null, + "not_in_indirect_" + ] + }, + "takes_params": [], + "uuid_attribute": "ipaentitlementid" + }, "group": { "aciattrs": [ "businesscategory", @@ -6063,6 +6146,7 @@ }, "service": { "aciattrs": [ + "ipakrbprincipalalias", "ipauniqueid", "krbcanonicalname", "krbextradata", @@ -6125,7 +6209,8 @@ "krbticketpolicyaux", "ipaobject", "ipaservice", - "pkiuser" + "pkiuser", + "ipakrbprincipal" ], "object_class_config": null, "object_name": "service", @@ -6807,6 +6892,99 @@ ], "uuid_attribute": "ipauniqueid" }, + "trust": { + "aciattrs": [ + "cn", + "ipantflatname", + "ipantsupportedencryptiontypes", + "ipanttrustattributes", + "ipanttrustauthincoming", + "ipanttrustauthoutgoing", + "ipanttrustdirection", + "ipanttrusteddomainsid", + "ipanttrustforesttrustinfo", + "ipanttrustpartner", + "ipanttrustposixoffset", + "ipanttrusttype", + "objectclass" + ], + "attribute_members": {}, + "bindable": false, + "container_dn": "cn=trusts", + "default_attributes": [ + "cn", + "ipantflatname", + "ipanttrusteddomainsid", + "ipanttrusttype", + "ipanttrustattributes", + "ipanttrustdirection", + "ipanttrustpartner", + "ipantauthtrustoutgoing", + "ipanttrustauthincoming", + "ipanttrustforesttrustinfo", + "ipanttrustposixoffset", + "ipantsupportedencryptiontypes" + ], + "hidden_attributes": [ + "objectclass", + "aci" + ], + "label": "Trusts", + "label_singular": "Trust", + "methods": [ + "add_ad", + "del", + "find", + "mod", + "show" + ], + "name": "trust", + "object_class": [ + "ipaNTTrustedDomain" + ], + "object_class_config": null, + "object_name": "trust", + "object_name_plural": "trusts", + "parent_object": "", + "primary_key": "cn", + "rdn_attribute": "", + "relationships": { + "member": [ + "Member", + "", + "no_" + ], + "memberindirect": [ + "Indirect Member", + null, + "no_indirect_" + ], + "memberof": [ + "Member Of", + "in_", + "not_in_" + ], + "memberofindirect": [ + "Indirect Member Of", + null, + "not_in_indirect_" + ] + }, + "takes_params": [ + { + "class": "Str", + "doc": "Realm name", + "flags": [], + "label": "Realm name", + "name": "cn", + "noextrawhitespace": true, + "primary_key": true, + "required": true, + "type": "unicode" + } + ], + "uuid_attribute": "" + }, "user": { "aciattrs": [ "audio", diff --git a/install/ui/test/data/trust_add.json b/install/ui/test/data/trust_add.json new file mode 100644 index 000000000..707eed27d --- /dev/null +++ b/install/ui/test/data/trust_add.json @@ -0,0 +1,9 @@ +{ + "error": null, + "id": null, + "result": { + "result": {}, + "summary": "Added Active Directory trust for realm \"ad.test\"", + "value": "ad.test" + } +} \ No newline at end of file diff --git a/install/ui/test/data/trust_find_pkeys.json b/install/ui/test/data/trust_find_pkeys.json new file mode 100644 index 000000000..353170c77 --- /dev/null +++ b/install/ui/test/data/trust_find_pkeys.json @@ -0,0 +1,17 @@ +{ + "error": null, + "id": null, + "result": { + "count": 1, + "result": [ + { + "cn": [ + "ad.test" + ], + "dn": "cn=ad.test,cn=ad,cn=trusts,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com" + } + ], + "summary": "1 trust matched", + "truncated": false + } +} \ No newline at end of file diff --git a/install/ui/test/data/trust_show.json b/install/ui/test/data/trust_show.json new file mode 100644 index 000000000..fa5ce3a0d --- /dev/null +++ b/install/ui/test/data/trust_show.json @@ -0,0 +1,67 @@ +{ + "error": null, + "id": null, + "result": { + "result": { + "attributelevelrights": { + "aci": "rscwo", + "cn": "rscwo", + "ipantflatname": "rscwo", + "ipantsupportedencryptiontypes": "rscwo", + "ipanttrustattributes": "rscwo", + "ipanttrustauthincoming": "rscwo", + "ipanttrustauthoutgoing": "rscwo", + "ipanttrustdirection": "rscwo", + "ipanttrusteddomainsid": "rscwo", + "ipanttrustforesttrustinfo": "rscwo", + "ipanttrustpartner": "rscwo", + "ipanttrustposixoffset": "rscwo", + "ipanttrusttype": "rscwo", + "nsaccountlock": "rscwo" + }, + "cn": [ + "ad.test" + ], + "dn": "cn=ad.test,cn=ad,cn=trusts,dc=idm,dc=lab,dc=bos,dc=redhat,dc=com", + "ipantflatname": [ + "AD" + ], + "ipanttrustattributes": [ + "136" + ], + "ipanttrustauthincoming": [ + { + "__base64__": "AQAAAAwAAAAwAAAAgKOs1XFQzQECAAAAEgAAAGEAYQBhAEEAQQBBADEAMQAxAAAA" + } + ], + "ipanttrustauthoutgoing": [ + { + "__base64__": "AQAAAAwAAAAwAAAAgKOs1XFQzQECAAAAEgAAAGEAYQBhAEEAQQBBADEAMQAxAAAA" + } + ], + "ipanttrustdirection": [ + "3" + ], + "ipanttrusteddomainsid": [ + "S-1-5-21-2085708479-1865276630-1146473440" + ], + "ipanttrustpartner": [ + "ad.test" + ], + "ipanttrusttype": [ + "2" + ], + "objectclass": [ + "ipaNTTrustedDomain" + ], + "trustdirection": [ + "Two-way trust" + ], + "trusttype": [ + "Active Directory domain" + ] + }, + "summary": null, + "value": "ad.test" + } +} \ No newline at end of file diff --git a/install/ui/trust.js b/install/ui/trust.js new file mode 100644 index 000000000..77e7cb381 --- /dev/null +++ b/install/ui/trust.js @@ -0,0 +1,165 @@ +/*jsl:import ipa.js */ + +/* Authors: + * Petr Vobornik + * + * Copyright (C) 2010 Red Hat + * see file 'COPYING' for use and warranty information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* REQUIRES: ipa.js, details.js, search.js, add.js, facet.js, entity.js */ + +IPA.trust = {}; + +IPA.trust.entity = function(spec) { + + var that = IPA.entity(spec); + + that.init = function() { + that.entity_init(); + + that.builder.search_facet({ + columns: [ + 'cn' + ] + }). + details_facet({ + sections: [ + { + name: 'details', + label: IPA.messages.objects.trust.details, + fields: [ + 'cn', + { + name: 'ipantflatname', + label: IPA.messages.objects.trust.ipantflatname, + read_only: true + }, + { + name: 'ipanttrusteddomainsid', + label: IPA.messages.objects.trust.ipanttrusteddomainsid, + read_only: true + }, + { + name: 'trustdirection', + label: IPA.messages.objects.trust.trustdirection + }, + { + name: 'trusttype', + label: IPA.messages.objects.trust.trusttype + } +// trust status not supported by show command at the moment +// { +// name: 'truststatus', +// label: IPA.messages.objects.trust.truststatus +// } + ] + } + ] + }). + adder_dialog({ + fields: [ + { + name: 'cn', + label: IPA.messages.objects.trust.domain, + widget: 'realm.realm_server' + }, + { + name: 'realm_admin', + label: IPA.messages.objects.trust.account, + widget: 'method.realm_admin' + }, + { + type: 'password', + name: 'realm_passwd', + label: IPA.messages.password.password, + widget: 'method.realm_passwd' + }, + { + type: 'password', + name: 'trust_secret', + label: IPA.messages.password.password, + widget: 'method.trust_secret' + }, + { + type: 'password', + name: 'trust_secret_verify', + label: IPA.messages.password.verify_password, + widget: 'method.trust_secret_verify', + flags: ['no_command'], + validators: [IPA.same_password_validator({ + other_field: 'trust_secret' + })] + } + ], + widgets: [ + { + type: 'details_table_section_nc', + name: 'realm', + widgets: [ + 'realm_server' + ] + }, + { + type: 'multiple_choice_section', + name: 'method', + label: IPA.messages.objects.trust.establish_using, + choices: [ + { + name: 'admin-account', + label: IPA.messages.objects.trust.admin_account, + fields: ['realm_admin', 'realm_passwd'], + required: ['realm_admin', 'realm_passwd'], + enabled: true + }, + { + name: 'preshared_password', + label: IPA.messages.objects.trust.preshared_password, + fields: ['trust_secret', 'trust_secret_verify'], + required: ['trust_secret', 'trust_secret_verify'] + } + ], + widgets: [ + { + name: 'realm_admin' + }, + { + type: 'password', + name: 'realm_passwd' + }, + { + type: 'password', + name: 'trust_secret' + }, + { + type: 'password', + name: 'trust_secret_verify' + } + ] + } + ], + policies: [ + IPA.multiple_choice_section_policy({ + widget: 'method' + }) + ] + }); + }; + + return that; +}; + +IPA.register('trust', IPA.trust.entity); diff --git a/install/ui/webui.js b/install/ui/webui.js index 5d32e7977..9b7c31be4 100644 --- a/install/ui/webui.js +++ b/install/ui/webui.js @@ -84,7 +84,8 @@ IPA.admin_navigation = function(spec) { ]}, {entity: 'selfservice'}, {entity: 'delegation'}, - {entity: 'config'} + {entity: 'config'}, + {entity: 'trust'} ]}]; var that = IPA.navigation(spec); diff --git a/install/ui/widget.js b/install/ui/widget.js index ccda2aef3..503897554 100644 --- a/install/ui/widget.js +++ b/install/ui/widget.js @@ -2669,12 +2669,80 @@ IPA.collapsible_section = function(spec) { IPA.details_section = IPA.collapsible_section; +IPA.layout = function(spec) { + return {}; +}; + +// Creates list of widgets into table with two columns: label and widget +IPA.table_layout = function(spec) { + + spec = spec || {}; + + var that = IPA.layout(spec); + that.table_class = spec.table_class || 'section-table'; + that.label_cell_class = spec.label_cell_class || 'section-cell-label'; + that.field_cell_class = spec.field_cell_class || 'section-cell-field'; + that.label_class = spec.label_class || 'field-label'; + that.field_class = spec.field_class || 'field'; + + that.create = function(widgets) { + + that.rows = $.ordered_map(); + + var table = $('', { + 'class': that.table_class + }); + + for (var i=0; i'); + that.rows.put(widget.name, tr); + + if (widget.hidden) { + tr.css('display', 'none'); + } + + tr.appendTo(table); + + var td = $('
', { + 'class': that.label_cell_class, + title: widget.label + }).appendTo(tr); + + $('', { + 'class': that.field_cell_class, + title: widget.label + }).appendTo(tr); + + var widget_container = $('
', { + name: widget.name, + 'class': that.field_class + }).appendTo(td); + + widget.create(widget_container); + } + return table; + }; + + return that; +}; + IPA.details_table_section = function(spec) { spec = spec || {}; var that = IPA.details_section(spec); - + that.layout = IPA.build_default(spec.layout, IPA.table_layout); that.action_panel = that.build_child(spec.action_panel); that.rows = $.ordered_map(); @@ -2686,50 +2754,10 @@ IPA.details_table_section = function(spec) { if (that.action_panel) { that.action_panel.create(container); } - - var table = $('', { - 'class': 'section-table' - }).appendTo(container); - var widgets = that.widgets.get_widgets(); - for (var i=0; i'); - that.add_row(widget.name, tr); - - if (widget.hidden) { - tr.css('display', 'none'); - } - - tr.appendTo(table); - - var td = $('
', { - 'class': 'section-cell-label', - title: widget.label - }).appendTo(tr); - - $('', { - 'class': 'section-cell-field', - title: widget.label - }).appendTo(tr); - - var widget_container = $('
', { - name: widget.name, - 'class': 'field' - }).appendTo(td); - - widget.create(widget_container); - } + var table = that.layout.create(widgets); + table.appendTo(container); + that.rows = that.layout.rows; }; @@ -2763,6 +2791,151 @@ IPA.details_table_section_nc = function(spec) { return that; }; +IPA.multiple_choice_section = function(spec) { + + spec = spec || {}; + + var that = IPA.composite_widget(spec); + that.choices = $.ordered_map().put_array(spec.choices, 'name'); + that.layout = IPA.build_default(spec.layout, IPA.table_layout); + + that.create = function(container) { + + var i, choice, choices; + + that.widget_create(container); + that.container.addClass('multiple-choice-section'); + + that.header_element = $('
', { + 'class': 'multiple-choice-section-header', + text: that.label + }).appendTo(container); + + that.choice_container = $('
', { + 'class': 'choices' + }).appendTo(container); + + choices = that.choices.values; + for (i=0; i',{ + 'class': 'choice', + name: choice.name + }); + + header = $('
',{ + 'class': 'choice-header' + }).appendTo(choice_el); + + enabled = choice.enabled !== undefined ? choice.enabled : false; + + radio_id = that.name + '_' + choice.name; + + $('',{ + type: 'radio', + name: that.name, + id: radio_id, + value: choice.name, + checked: enabled, + change: function() { + that.select_choice(this.value); + } + }).appendTo(header); + + $('