Assorted UI fixes:

- Change sort functions to be on entities, so can use on the view pages too
- Fix bug: empty ajax search on useredit blows up
- Filter illegal characters from suggest uid/email methods
- Rename first/last name fields
- Make default font family sans-serif
- Speed up effect appear/fade rendering
- Add buttons to top and bottom of pages
- Make grouplist sortable
- Add noscript warning to welcome page
This commit is contained in:
Kevin McCarthy committed 2007-10-02 10:49:44 -07:00
1 parent 050157ace0
commit 5750ebdd83
14 files changed
+122 -44

No files matched your search

+28 -17
View File
@@ -1,6 +1,7 @@
import random import random
from pickle import dumps, loads from pickle import dumps, loads
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
import re
import os import os
import cherrypy import cherrypy
@@ -50,40 +51,40 @@ def utf8_encode(value):
def sort_group_member(a, b): def sort_group_member(a, b):
"""Comparator function used for sorting group members.""" """Comparator function used for sorting group members."""
if a.get('uid') and b.get('uid'): if a.getValue('uid') and b.getValue('uid'):
if a.get('givenname', '') == b.get('givenname', ''): if a.getValue('givenname') == b.getValue('givenname'):
if a.get('sn', '') == b.get('sn', ''): if a.getValue('sn') == b.getValue('sn'):
if a.get('uid') == b.get('uid'): if a.getValue('uid') == b.getValue('uid'):
return 0 return 0
elif a.get('uid') < b.get('uid'): elif a.getValue('uid') < b.getValue('uid'):
return -1 return -1
else: else:
return 1 return 1
elif a.get('sn', '') < b.get('sn', ''): elif a.getValue('sn') < b.getValue('sn'):
return -1 return -1
else: else:
return 1 return 1
elif a.get('givenname') < b.get('givenname'): elif a.getValue('givenname') < b.getValue('givenname'):
return -1 return -1
else: else:
return 1 return 1
elif a.get('uid'): elif a.getValue('uid'):
return -1 return -1
elif b.get('uid'): elif b.getValue('uid'):
return 1 return 1
else: else:
if a.get('cn', '') == b.get('cn', ''): if a.getValue('cn') == b.getValue('cn'):
return 0 return 0
elif a.get('cn', '') < b.get('cn', ''): elif a.getValue('cn') < b.getValue('cn'):
return -1 return -1
else: else:
return 1 return 1
def sort_by_cn(a, b): def sort_by_cn(a, b):
"""Comparator function used for sorting groups.""" """Comparator function used for sorting groups."""
if a.get('cn', '') == b.get('cn', ''): if a.getValue('cn') == b.getValue('cn'):
return 0 return 0
elif a.get('cn', '') < b.get('cn', ''): elif a.getValue('cn') < b.getValue('cn'):
return -1 return -1
else: else:
return 1 return 1
@@ -216,7 +217,7 @@ class Root(controllers.RootController):
This method is used for the ajax search on the user edit page.""" This method is used for the ajax search on the user edit page."""
client.set_krbccache(os.environ["KRB5CCNAME"]) client.set_krbccache(os.environ["KRB5CCNAME"])
groups = [] groups = []
counter = 0 groups_counter = 0
searchlimit = 100 searchlimit = 100
criteria = kw.get('criteria') criteria = kw.get('criteria')
if criteria != None and len(criteria) > 0: if criteria != None and len(criteria) > 0:
@@ -248,8 +249,8 @@ class Root(controllers.RootController):
del(user_dict['userpassword']) del(user_dict['userpassword'])
user_groups = client.get_groups_by_member(user.dn, ['dn', 'cn']) user_groups = client.get_groups_by_member(user.dn, ['dn', 'cn'])
user_groups.sort(sort_by_cn)
user_groups_dicts = map(lambda group: group.toDict(), user_groups) user_groups_dicts = map(lambda group: group.toDict(), user_groups)
user_groups_dicts.sort(sort_by_cn)
user_groups_data = b64encode(dumps(user_groups_dicts)) user_groups_data = b64encode(dumps(user_groups_dicts))
# store a copy of the original user for the update later # store a copy of the original user for the update later
@@ -421,8 +422,10 @@ class Root(controllers.RootController):
try: try:
user = client.get_user_by_uid(uid, user_fields) user = client.get_user_by_uid(uid, user_fields)
user_groups = client.get_groups_by_member(user.dn, ['cn']) user_groups = client.get_groups_by_member(user.dn, ['cn'])
user_groups.sort(sort_by_cn)
user_reports = client.get_users_by_manager(user.dn, user_reports = client.get_users_by_manager(user.dn,
['givenname', 'sn', 'uid']) ['givenname', 'sn', 'uid'])
user_reports.sort(sort_group_member)
user_manager = None user_manager = None
try: try:
@@ -466,6 +469,10 @@ class Root(controllers.RootController):
@expose() @expose()
@identity.require(identity.not_anonymous()) @identity.require(identity.not_anonymous())
def suggest_uid(self, givenname, sn): def suggest_uid(self, givenname, sn):
# filter illegal uid characters out
givenname = re.sub(r'[^a-zA-Z_\-0-9]', "", givenname)
sn = re.sub(r'[^a-zA-Z_\-0-9]', "", sn)
if (len(givenname) == 0) or (len(sn) == 0): if (len(givenname) == 0) or (len(sn) == 0):
return "" return ""
@@ -512,6 +519,10 @@ class Root(controllers.RootController):
@expose() @expose()
@identity.require(identity.not_anonymous()) @identity.require(identity.not_anonymous())
def suggest_email(self, givenname, sn): def suggest_email(self, givenname, sn):
# remove illegal email characters
givenname = re.sub(r'[^a-zA-Z0-9!#\$%\*/?\|\^\{\}`~&\'\+\-=_]', "", givenname)
sn = re.sub(r'[^a-zA-Z0-9!#\$%\*/?\|\^\{\}`~&\'\+\-=_]', "", sn)
if (len(givenname) == 0) or (len(sn) == 0): if (len(givenname) == 0) or (len(sn) == 0):
return "" return ""
@@ -716,11 +727,11 @@ class Root(controllers.RootController):
lambda dn: client.get_user_by_dn(dn, ['dn', 'givenname', 'sn', lambda dn: client.get_user_by_dn(dn, ['dn', 'givenname', 'sn',
'uid', 'cn']), 'uid', 'cn']),
member_dns) member_dns)
members.sort(sort_group_member)
# Map users into an array of dicts, which can be serialized # Map users into an array of dicts, which can be serialized
# (so we don't have to do this on each round trip) # (so we don't have to do this on each round trip)
member_dicts = map(lambda member: member.toDict(), members) member_dicts = map(lambda member: member.toDict(), members)
member_dicts.sort(sort_group_member)
# store a copy of the original group for the update later # store a copy of the original group for the update later
group_data = b64encode(dumps(group_dict)) group_data = b64encode(dumps(group_dict))
@@ -885,8 +896,8 @@ class Root(controllers.RootController):
lambda dn: client.get_user_by_dn(dn, ['dn', 'givenname', 'sn', lambda dn: client.get_user_by_dn(dn, ['dn', 'givenname', 'sn',
'uid', 'cn']), 'uid', 'cn']),
member_dns) member_dns)
members.sort(sort_group_member)
member_dicts = map(lambda member: member.toDict(), members) member_dicts = map(lambda member: member.toDict(), members)
member_dicts.sort(sort_group_member)
return dict(group=group_dict, fields=forms.group.GroupFields(), return dict(group=group_dict, fields=forms.group.GroupFields(),
members = member_dicts) members = member_dicts)
+3 -3
View File
@@ -8,9 +8,9 @@ class UserFields():
label="Confirm Password") label="Confirm Password")
uidnumber = widgets.TextField(name="uidnumber", label="UID") uidnumber = widgets.TextField(name="uidnumber", label="UID")
gidnumber = widgets.TextField(name="gidnumber", label="GID") gidnumber = widgets.TextField(name="gidnumber", label="GID")
givenname = widgets.TextField(name="givenname", label="First name") givenname = widgets.TextField(name="givenname", label="Given Name")
sn = widgets.TextField(name="sn", label="Last name") sn = widgets.TextField(name="sn", label="Family Name")
mail = widgets.TextField(name="mail", label="E-mail address") mail = widgets.TextField(name="mail", label="E-mail Address")
telephonenumber = widgets.TextField(name="telephonenumber", label="Phone") telephonenumber = widgets.TextField(name="telephonenumber", label="Phone")
# nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated") # nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated")
nsAccountLock = widgets.SingleSelectField(name="nsAccountLock", nsAccountLock = widgets.SingleSelectField(name="nsAccountLock",
@@ -7,6 +7,7 @@ html, body {
background:#fff; background:#fff;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: sans-serif;
} }
body { body {
@@ -102,7 +102,7 @@ function addmember(dn, info) {
var undolink = document.createElement('a'); var undolink = document.createElement('a');
undolink.setAttribute('href', ''); undolink.setAttribute('href', '');
undolink.setAttribute('onclick', undolink.setAttribute('onclick',
'new Effect.Fade(Element.up(this), {afterFinish: removeElement});' + 'new Effect.Fade(Element.up(this), {afterFinish: removeElement, duration: 0.75});' +
'added_hash.remove("' + jsStringEscape(dn) + '");' + 'added_hash.remove("' + jsStringEscape(dn) + '");' +
'return false;'); 'return false;');
undolink.appendChild(document.createTextNode("undo")); undolink.appendChild(document.createTextNode("undo"));
@@ -123,8 +123,8 @@ function addmember(dn, info) {
function addmemberHandler(element, dn, info) { function addmemberHandler(element, dn, info) {
var newdiv = addmember(dn, info) var newdiv = addmember(dn, info)
if (newdiv != null) { if (newdiv != null) {
new Effect.Fade(Element.up(element)); new Effect.Fade(Element.up(element), {duration: 0.75});
new Effect.Appear(newdiv); new Effect.Appear(newdiv, {duration: 0.75});
/* Element.up(element).remove(); */ /* Element.up(element).remove(); */
} }
} }
@@ -139,8 +139,8 @@ function removemember(dn, info) {
var undolink = document.createElement('a'); var undolink = document.createElement('a');
undolink.setAttribute('href', ''); undolink.setAttribute('href', '');
undolink.setAttribute('onclick', undolink.setAttribute('onclick',
'new Effect.Fade(Element.up(this), {afterFinish: removeElement});' + 'new Effect.Fade(Element.up(this), {afterFinish: removeElement, duration: 0.75});' +
"new Effect.Appear($('" + orig_div_id + "'));" + "new Effect.Appear($('" + orig_div_id + "'), {duration: 0.75});" +
'return false;'); 'return false;');
undolink.appendChild(document.createTextNode("undo")); undolink.appendChild(document.createTextNode("undo"));
newdiv.appendChild(undolink); newdiv.appendChild(undolink);
@@ -159,8 +159,8 @@ function removemember(dn, info) {
function removememberHandler(element, dn, info) { function removememberHandler(element, dn, info) {
var newdiv = removemember(dn, info); var newdiv = removemember(dn, info);
new Effect.Fade(Element.up(element)); new Effect.Fade(Element.up(element), {duration: 0.75});
new Effect.Appear(newdiv); new Effect.Appear(newdiv, {duration: 0.75});
/* Element.up(element).remove(); */ /* Element.up(element).remove(); */
} }
@@ -291,7 +291,9 @@ TableKit.Sortable = {
if(cell.hasClassName(op.noSortClass)) {return;} if(cell.hasClassName(op.noSortClass)) {return;}
order = order ? order : (cell.hasClassName(op.descendingClass) ? 1 : -1); // order = order ? order : (cell.hasClassName(op.descendingClass) ? 1 : -1);
// kmccarth - change default sort order to ascending
order = order ? order : (cell.hasClassName(op.ascendingClass) ? -1 : 1);
var rows = TableKit.getBodyRows(table); var rows = TableKit.getBodyRows(table);
if(cell.hasClassName(op.ascendingClass) || cell.hasClassName(op.descendingClass)) { if(cell.hasClassName(op.ascendingClass) || cell.hasClassName(op.descendingClass)) {
@@ -843,4 +845,4 @@ if(window.FastInit) {
FastInit.addOnLoad(TableKit.load); FastInit.addOnLoad(TableKit.load);
} else { } else {
Event.observe(window, 'load', TableKit.load); Event.observe(window, 'load', TableKit.load);
} }
@@ -66,7 +66,7 @@ from ipagui.helpers import ipahelper
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
if (results_counter == 0) { if (results_counter == 0) {
var message = "No results found for " + search_string; var message = "No results found for '" + search_string + "'";
} else { } else {
var message = results_counter + " results found:"; var message = results_counter + " results found:";
} }
@@ -3,6 +3,19 @@
<form action="${action}" name="${name}" method="${method}" class="tableform" <form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()" > onsubmit="preSubmit()" >
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<input type="submit" class="submitbutton" name="submit"
value="Update Group"/>
</th>
<td>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
</td>
</tr>
</table>
<?python <?python
from ipagui.helpers import ipahelper from ipagui.helpers import ipahelper
?> ?>
@@ -163,8 +176,6 @@ from ipagui.helpers import ipahelper
</div> </div>
</div> </div>
<table class="formtable" cellpadding="2" cellspacing="0" border="0"> <table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
<th> <th>
@@ -6,6 +6,7 @@
<title>Find Groups</title> <title>Find Groups</title>
</head> </head>
<body> <body>
<script type="text/javascript" charset="utf-8" src="${tg.url('/static/javascript/tablekit.js')}"></script>
<div id="search"> <div id="search">
<form action="${tg.url('/grouplist')}" method="get"> <form action="${tg.url('/grouplist')}" method="get">
<input id="criteria" type="text" name="criteria" value="${criteria}" /> <input id="criteria" type="text" name="criteria" value="${criteria}" />
@@ -17,15 +18,18 @@
</div> </div>
<div py:if='(groups != None) and (len(groups) > 0)'> <div py:if='(groups != None) and (len(groups) > 0)'>
<h2>${len(groups)} results returned:</h2> <h2>${len(groups)} results returned:</h2>
<table id="resultstable"> <table id="resultstable" class="sortable resizable">
<thead>
<tr> <tr>
<th> <th>
<label class="fieldlabel" py:content="fields.cn.label" /> ${fields.cn.label}
</th> </th>
<th> <th>
<label class="fieldlabel" py:content="fields.description.label" /> ${fields.description.label}
</th> </th>
</tr> </tr>
</thead>
<tbody>
<tr py:for="group in groups"> <tr py:for="group in groups">
<td> <td>
<a href="${tg.url('/groupshow',cn=group.cn)}">${group.cn}</a> <a href="${tg.url('/groupshow',cn=group.cn)}">${group.cn}</a>
@@ -34,6 +38,7 @@
${group.description} ${group.description}
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
<div py:if='(groups != None) and (len(groups) == 0)'> <div py:if='(groups != None) and (len(groups) == 0)'>
@@ -3,6 +3,14 @@
<form action="${action}" name="${name}" method="${method}" class="tableform" <form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()" > onsubmit="preSubmit()" >
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<td>
<input type="submit" class="submitbutton" name="submit" value="Add Group"/>
</td>
</tr>
</table>
<?python <?python
from ipagui.helpers import ipahelper from ipagui.helpers import ipahelper
?> ?>
@@ -91,7 +99,6 @@ from ipagui.helpers import ipahelper
<table class="formtable" cellpadding="2" cellspacing="0" border="0"> <table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
<th></th>
<td> <td>
<br /> <br />
<input type="submit" class="submitbutton" name="submit" value="Add Group"/> <input type="submit" class="submitbutton" name="submit" value="Add Group"/>
@@ -6,8 +6,15 @@
<title>View Group</title> <title>View Group</title>
</head> </head>
<body> <body>
<?python
edit_url = tg.url('/groupedit', cn=group.get('cn'))
?>
<h2>View Group</h2> <h2>View Group</h2>
<input type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Group" />
<div class="formsection">Group Details</div> <div class="formsection">Group Details</div>
<table class="formtable" cellpadding="2" cellspacing="0" border="0"> <table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
@@ -60,10 +67,11 @@
</span> </span>
</div> </div>
<br/>
<br/> <br/>
<a href="${tg.url('/groupedit', cn=group.get('cn'))}">edit</a> <input type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Group" />
</body> </body>
</html> </html>
@@ -3,6 +3,20 @@
<form action="${action}" name="${name}" method="${method}" class="tableform" <form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()"> onsubmit="preSubmit()">
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<th>
<input type="submit" class="submitbutton" name="submit"
value="Update Person"/>
</th>
<td>
<input type="submit" class="submitbutton" name="submit"
value="Cancel Edit" />
</td>
<td></td>
</tr>
</table>
<?python <?python
from ipagui.helpers import ipahelper from ipagui.helpers import ipahelper
?> ?>
@@ -314,9 +328,6 @@ from ipagui.helpers import ipahelper
</div> </div>
</div> </div>
<table class="formtable" cellpadding="2" cellspacing="0" border="0"> <table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
<th> <th>
@@ -3,6 +3,14 @@
<form action="${action}" name="${name}" method="${method}" class="tableform" <form action="${action}" name="${name}" method="${method}" class="tableform"
onsubmit="preSubmit()"> onsubmit="preSubmit()">
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr>
<td>
<input type="submit" class="submitbutton" name="submit" value="Add Person"/>
</td>
</tr>
</table>
<?python <?python
from ipagui.helpers import ipahelper from ipagui.helpers import ipahelper
?> ?>
@@ -240,7 +248,6 @@ from ipagui.helpers import ipahelper
<table class="formtable" cellpadding="2" cellspacing="0" border="0"> <table class="formtable" cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
<th></th>
<td> <td>
<br /> <br />
<input type="submit" class="submitbutton" name="submit" value="Add Person"/> <input type="submit" class="submitbutton" name="submit" value="Add Person"/>
@@ -6,8 +6,15 @@
<title>View Person</title> <title>View Person</title>
</head> </head>
<body> <body>
<?python
edit_url = tg.url('/useredit', uid=user.get('uid'))
?>
<h2>View Person</h2> <h2>View Person</h2>
<input type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Person" />
<?python <?python
from ipagui.helpers import userhelper from ipagui.helpers import userhelper
pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration")) pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration"))
@@ -110,10 +117,11 @@ else:
<a href="${tg.url('/groupshow', cn=group.cn)}">${group.cn}</a> <a href="${tg.url('/groupshow', cn=group.cn)}">${group.cn}</a>
</div> </div>
<br/>
<br/> <br/>
<a href="${tg.url('/useredit', uid=user.get('uid'))}">edit</a> <input type="button"
onclick="document.location.href='${edit_url}'"
value="Edit Person" />
</body> </body>
</html> </html>
@@ -11,6 +11,13 @@
py:content="XML(tg_flash)"></div> py:content="XML(tg_flash)"></div>
<h1>Welcome to Free IPA</h1> <h1>Welcome to Free IPA</h1>
<noscript>
<span class="warning_message">
This site makes heavy use of JavaScript.<br />
Please enable JavaScript in your browser to make sure all pages function properly.
</span>
</noscript>
<p> <p>
IPA is used to manage Identity, Policy, and Auditing for your IPA is used to manage Identity, Policy, and Auditing for your
organization. organization.