This commit is contained in:
Mike McGrath 2007-07-06 09:24:13 -05:00
commit 037a2c2b6e
128 changed files with 11813 additions and 0 deletions

9
fas/README.txt Normal file
View file

@ -0,0 +1,9 @@
fas
This is a TurboGears (http://www.turbogears.org) project. It can be
started by running the start-fas.py script.
LDAP Dump / restore:
ldapsearch -x -D 'cn=directory manager' -b 'dc=fedoraproject,dc=org' "objectclass=*" \* aci > LDAPDump
ldapadd -x -D 'cn=directory manager' -f LDAPDump -W

71
fas/dev.cfg Normal file
View file

@ -0,0 +1,71 @@
[global]
# This is where all of your settings go for your development environment
# Settings that are the same for both development and production
# (such as template engine, encodings, etc.) all go in
# fas/config/app.cfg
mail.on = True
mail.server = 'bastion.fedora.phx.redhat.com'
base_url_filter.base_url = "http://192.168.2.101:8080"
base_url_filter.use_x_forwarded_host = True
# DATABASE
# pick the form for your database
# sqlobject.dburi="postgres://username@hostname/databasename"
# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
# sqlobject.dburi="sqlite:///file_name_and_path"
# If you have sqlite, here's a simple default to get you started
# in development
sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite?debug=True"
# if you are using a database or table type without transactions
# (MySQL default, for example), you should turn off transactions
# by prepending notrans_ on the uri
# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
# for Windows users, sqlite URIs look like:
# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
# SERVER
# Some server parameters that you may want to tweak
server.socket_port=8080
# Enable the debug output at the end on pages.
# log_debug_info_filter.on = False
server.environment="development"
autoreload.package="fas"
# session_filter.on = True
# Set to True if you'd like to abort execution if a controller gets an
# unexpected parameter. False by default
tg.strict_parameters = True
server.webpath='/fas'
base_url_filter.on=True
# LOGGING
# Logging configuration generally follows the style of the standard
# Python logging module configuration. Note that when specifying
# log format messages, you need to use *() for formatting variables.
# Deployment independent log configuration is in fas/config/log.cfg
[logging]
[[loggers]]
[[[fas]]]
level='DEBUG'
qualname='fas'
handlers=['debug_out']
[[[allinfo]]]
level='INFO'
handlers=['debug_out']
[[[access]]]
level='INFO'
qualname='turbogears.access'
handlers=['access_out']
propagate=0

BIN
fas/devdata.sqlite Normal file

Binary file not shown.

BIN
fas/devdata.sqlite.bak Normal file

Binary file not shown.

15
fas/fas.egg-info/PKG-INFO Normal file
View file

@ -0,0 +1,15 @@
Metadata-Version: 1.0
Name: fas
Version: 1.0
Summary: UNKNOWN
Home-page: UNKNOWN
Author: UNKNOWN
Author-email: UNKNOWN
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
Classifier: Development Status :: 3 - Alpha
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Framework :: TurboGears

View file

@ -0,0 +1,21 @@
README.txt
setup.py
start-fas.py
fas/__init__.py
fas/controllers.py
fas/json.py
fas/model.py
fas/release.py
fas.egg-info/PKG-INFO
fas.egg-info/SOURCES.txt
fas.egg-info/dependency_links.txt
fas.egg-info/not-zip-safe
fas.egg-info/paster_plugins.txt
fas.egg-info/requires.txt
fas.egg-info/sqlobject.txt
fas.egg-info/top_level.txt
fas/config/__init__.py
fas/templates/__init__.py
fas/tests/__init__.py
fas/tests/test_controllers.py
fas/tests/test_model.py

View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,2 @@
TurboGears
PasteScript

View file

@ -0,0 +1 @@
TurboGears >= 1.0.1

View file

@ -0,0 +1,2 @@
db_module=fas.model
history_dir=$base/fas/sqlobject-history

View file

@ -0,0 +1 @@
fas

0
fas/fas/__init__.py Normal file
View file

BIN
fas/fas/__init__.pyc Normal file

Binary file not shown.

View file

132
fas/fas/config/app.cfg Normal file
View file

@ -0,0 +1,132 @@
[global]
# The settings in this file should not vary depending on the deployment
# environment. dev.cfg and prod.cfg are the locations for
# the different deployment settings. Settings in this file will
# be overridden by settings in those other files.
# The commented out values below are the defaults
# VIEW
# which view (template engine) to use if one is not specified in the
# template name
# tg.defaultview = "kid"
# The following kid settings determine the settings used by the kid serializer.
# One of (html|html-strict|xhtml|xhtml-strict|xml|json)
# kid.outputformat="html"
# kid.encoding="utf-8"
# The sitetemplate is used for overall styling of a site that
# includes multiple TurboGears applications
# tg.sitetemplate="<packagename.templates.templatename>"
# Allow every exposed function to be called as json,
# tg.allow_json = False
# List of Widgets to include on every page.
# for exemple ['turbogears.mochikit']
# tg.include_widgets = []
# Set to True if the scheduler should be started
# tg.scheduler = False
# VISIT TRACKING
# Each visit to your application will be assigned a unique visit ID tracked via
# a cookie sent to the visitor's browser.
# --------------
# Enable Visit tracking
visit.on=True
# Number of minutes a visit may be idle before it expires.
visit.timeout=20
# The name of the cookie to transmit to the visitor's browser.
# visit.cookie.name="tg-visit"
# Domain name to specify when setting the cookie (must begin with . according to
# RFC 2109). The default (None) should work for most cases and will default to
# the machine to which the request was made. NOTE: localhost is NEVER a valid
# value and will NOT WORK.
# visit.cookie.domain=None
# Specific path for the cookie
# visit.cookie.path="/"
# The name of the VisitManager plugin to use for visitor tracking.
visit.manager="safas"
#visit.manager="sqlobject"
# Database class to use for visit tracking
visit.saprovider.model = "fedora.accounts.tgfas.Visit"
#visit.soprovider.model = "fas.model.Visit"
identity.saprovider.model.visit="fedora.accounts.tgfas2.VisitIdentity"
sqlalchemy.dburi='sqlite://'
# IDENTITY
# General configuration of the TurboGears Identity management module
# --------
# Switch to turn on or off the Identity management module
identity.on=True
# [REQUIRED] URL to which CherryPy will internally redirect when an access
# control check fails. If Identity management is turned on, a value for this
# option must be specified.
identity.failure_url="/login"
identity.provider='safas2'
# identity.provider='sqlobject'
# The names of the fields on the login form containing the visitor's user ID
# and password. In addition, the submit button is specified simply so its
# existence may be stripped out prior to passing the form data to the target
# controller.
# identity.form.user_name="user_name"
# identity.form.password="password"
# identity.form.submit="login"
# What sources should the identity provider consider when determining the
# identity associated with a request? Comma separated list of identity sources.
# Valid sources: form, visit, http_auth
# identity.source="form,http_auth,visit"
# SqlObjectIdentityProvider
# Configuration options for the default IdentityProvider
# -------------------------
# The classes you wish to use for your Identity model. Remember to not use reserved
# SQL keywords for class names (at least unless you specify a different table
# name using sqlmeta).
#identity.soprovider.model.user="fas.model.User"
#identity.soprovider.model.group="fas.model.Group"
#identity.soprovider.model.permission="fas.model.Permission"
#identity.soprovider.model.autocreate="True"
# The password encryption algorithm used when comparing passwords against what's
# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
# specify an encryption algorithm, passwords are expected to be clear text.
# The SqlObjectProvider *will* encrypt passwords supplied as part of your login
# form. If you set the password through the password property, like:
# my_user.password = 'secret'
# the password will be encrypted in the database, provided identity is up and
# running, or you have loaded the configuration specifying what encryption to
# use (in situations where identity may not yet be running, like tests).
# identity.soprovider.encryption_algorithm=None
# compress the data sends to the web browser
# [/]
# gzip_filter.on = True
# gzip_filter.mime_types = ["application/x-javascript", "text/javascript", "text/html", "text/css", "text/plain"]
[/static]
static_filter.on = True
static_filter.dir = "%(top_level_dir)s/static"
[/favicon.ico]
static_filter.on = True
static_filter.file = "%(top_level_dir)s/static/images/favicon.ico"

29
fas/fas/config/log.cfg Normal file
View file

@ -0,0 +1,29 @@
# LOGGING
# Logging is often deployment specific, but some handlers and
# formatters can be defined here.
[logging]
[[formatters]]
[[[message_only]]]
format='*(message)s'
[[[full_content]]]
format='*(asctime)s *(name)s *(levelname)s *(message)s'
[[handlers]]
[[[debug_out]]]
class='StreamHandler'
level='DEBUG'
args='(sys.stdout,)'
formatter='full_content'
[[[access_out]]]
class='StreamHandler'
level='INFO'
args='(sys.stdout,)'
formatter='message_only'
[[[error_out]]]
class='StreamHandler'
level='ERROR'
args='(sys.stdout,)'

481
fas/fas/controllers.py Normal file
View file

@ -0,0 +1,481 @@
from turbogears import controllers, expose
# from model import *
from turbogears import identity, redirect, widgets, validate, validators, error_handler
from cherrypy import request, response
from fas.fasLDAP import UserAccount
from fas.fasLDAP import Person
from fas.fasLDAP import Groups
from fas.fasLDAP import UserGroup
from turbogears import exception_handler
import turbogears
import ldap
# from fas import json
# import logging
# log = logging.getLogger("fas.controllers")
class knownUser(validators.FancyValidator):
def _to_python(self, value, state):
return value.strip()
def validate_python(self, value, state):
p = Person.byUserName(value)
if p.cn:
raise validators.Invalid("'%s' already axists" % value, value, state)
class newPerson(widgets.WidgetsList):
# cn = widgets.TextField(label='Username', validator=validators.PlainText(not_empty=True, max=10))
cn = widgets.TextField(label='Username', validator=validators.All(knownUser(not_empty=True, max=10), validators.String(max=32, min=3)))
givenName = widgets.TextField(label='Full Name', validator=validators.String(not_empty=True, max=42))
mail = widgets.TextField(label='email', validator=validators.Email(not_empty=True, strip=True))
telephoneNumber = widgets.TextField(label='Telephone Number', validator=validators.PhoneNumber(not_empty=True))
postalAddress = widgets.TextArea(label='Postal Address', validator=validators.NotEmpty)
newPersonForm = widgets.TableForm(fields=newPerson(), submit_text='Sign Up')
class findUser(widgets.WidgetsList):
userName = widgets.AutoCompleteField(label='Username', search_controller='search', search_param='userName', result_name='people')
action = widgets.HiddenField(label='action', default='apply', validator=validators.String(not_empty=True))
groupName = widgets.HiddenField(label='groupName', validator=validators.String(not_empty=True))
searchUserForm = widgets.TableForm(fields=findUser(), submit_text='Invite')
class Root(controllers.RootController):
@expose(template="fas.templates.error")
def errorMessage(self, tg_exceptions=None):
''' Generic exception handler'''
# Maybe add a popup or alert or some damn thing.
message = '%s' % tg_exceptions
return dict(handling_value=True,exception=message)
@expose(template="fas.templates.welcome")
# @identity.require(identity.in_group("admin"))
def index(self):
import time
# log.debug("Happy TurboGears Controller Responding For Duty")
return dict(now=time.ctime())
@expose(template="fas.templates.home")
def home(self):
from feeds import Koji
builds = Koji(turbogears.identity.current.user_name)
return dict(builds=builds)
@expose(template="fas.templates.login")
def login(self, forward_url=None, previous_url=None, *args, **kw):
if not identity.current.anonymous \
and identity.was_login_attempted() \
and not identity.get_identity_errors():
raise redirect(forward_url)
forward_url=None
previous_url= request.path
if identity.was_login_attempted():
msg=_("The credentials you supplied were not correct or "
"did not grant access to this resource.")
elif identity.get_identity_errors():
msg=_("You must provide your credentials before accessing "
"this resource.")
else:
msg=_("Please log in.")
forward_url= request.headers.get("Referer", "/")
response.status=403
return dict(message=msg, previous_url=previous_url, logging_in=True,
original_parameters=request.params,
forward_url=forward_url)
@expose()
def logout(self):
identity.current.logout()
raise redirect("/")
@expose(template="fas.templates.editAccount")
@identity.require(identity.not_anonymous())
def editAccount(self,userName=None, action=None):
if userName:
try:
Groups.byUserName(turbogears.identity.current.user_name)['accounts'].cn
if not userName:
userName = turbogears.identity.current.user_name
except KeyError:
turbogears.flash('You cannot view %s' % userName )
userName = turbogears.identity.current.user_name
else:
userName = turbogears.identity.current.user_name
user = Person.byUserName(userName)
groups = Groups.byUserName(userName)
groupsPending = Groups.byUserName(userName, unapprovedOnly=True)
try:
groups['cla_done']
claDone=True
except KeyError:
claDone=None
return dict(user=user, groups=groups, groupsPending=groupsPending, action=action, claDone=claDone)
@expose(template="fas.templates.editGroup")
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def editGroup(self, groupName):
try:
groups = Groups.byGroupName(groupName, includeUnapproved=True)
except KeyError, e:
raise ValueError, 'Group: %s - Does not exist!' % e
group = Groups.groups(groupName)[groupName]
userName = turbogears.identity.current.user_name
try:
myStatus = groups[userName].fedoraRoleStatus
except KeyError:
# Not in group
myStatus = 'Not a Member'
try:
me = groups[userName]
except:
me = UserGroup()
#searchUserForm.groupName.display('group')
#findUser.groupName.display(value='fff')
value = {'groupName' : group.cn}
return dict(groups=groups, group=group, me=me, searchUserForm=searchUserForm, value=value)
@expose(template="fas.templates.groupList")
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def listGroup(self, search='*'):
groups = Groups.groups(search)
userName = turbogears.identity.current.user_name
myGroups = Groups.byUserName(userName)
try:
groups.keys()
except:
turbogears.flash("No Groups found matching '%s'" % search)
groups = {}
return dict(groups=groups, search=search, myGroups=myGroups)
@expose(template="fas.templates.resetPassword")
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
def resetPassword(self, userName=None, password=None, passwordCheck=None, mail=None):
import turbomail
# Logged in
if turbogears.identity.current.user_name and not password:
return dict()
# Not logged in
if not (userName and mail) and not turbogears.identity.current.user_name:
# turbogears.flash('Please provide your username and password')
return dict()
if turbogears.identity.current.user_name:
userName = turbogears.identity.current.user_name
p = Person.byUserName(userName)
if password and passwordCheck:
if not password == passwordCheck:
turbogears.flash('Passwords do not match!')
return dict()
if len(password) < 8:
turbogears.flash('Password is too short. Must be at least 8 characters long')
return dict()
newpass = p.generatePassword(password)
if userName and mail and not turbogears.identity.current.user_name:
if not mail == p.mail:
turbogears.flash("username + email combo unknown.")
return dict()
newpass = p.generatePassword()
message = turbomail.Message('accounts@fedoraproject.org', p.mail, 'Fedora Project Password Reset')
message.plain = "You have requested a password reset! Your new password is - %s \nPlease go to https://admin.fedoraproject.org/fas/ to change it" % newpass['pass']
turbomail.enqueue(message)
p.__setattr__('userPassword', newpass['hash'])
p.userPassword = newpass['hash']
print "PASS: %s" % newpass['pass']
if turbogears.identity.current.user_name:
turbogears.flash("Password Changed")
turbogears.redirect("editAccount")
else:
turbogears.flash('Your password has been emailed to you')
return dict()
@expose(template="fas.templates.userList")
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
# @identity.require(identity.in_group("accounts"))
def listUser(self, search='a*'):
users = Person.users(search)
try:
users[0]
except:
turbogears.flash("No users found matching '%s'" % search)
users = []
return dict(printList=users, search=search)
listUsers = listUser
@expose(template='fas.templates.edit')
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def editUserAttribute(self, attribute, value, userName=None):
try:
Groups.byUserName(turbogears.identity.current.user_name)['accounts'].cn
if not userName:
userName = turbogears.identity.current.user_name
except KeyError:
turbogears.flash('You cannot view %s' % userName )
userName = turbogears.identity.current.user_name
attribute = attribute.encode('utf8')
value = value.encode('utf8')
if attribute and value:
p = Person.byUserName(userName)
p.__setattr__(attribute, value)
turbogears.flash("'%s' Updated to %s" % (attribute, value))
if userName == turbogears.identity.current.user_name:
turbogears.redirect('editAccount')
else:
turbogears.redirect('editAccount?userName=%s' % userName)
return dict(userName=userName, attribute=attribute, value=value)
# @expose(template='fas.templates.apply')
# @exception_handler(errorMessage, rules="isinstance(tg_exceptions,ValueError)")
# @identity.require(identity.not_anonymous())
# def sudo(self, userName):
# # This doesn't work
# turbogears.identity.current.user_name=userName
# turbogears.flash('Sudoed to %s' % userName)
# turbogears.recirect('editAccount')
# @error_handler(editGroup)
# @validate(form=newPersonForm)
@expose(template='fas.templates.apply')
@identity.require(identity.not_anonymous())
def modifyGroup(self, groupName, action, userName, **kw):
''' Modifies group based on action, groupName and userName '''
try:
userName = userName['text']
except TypeError:
pass
sponsor = turbogears.identity.current.user_name
try:
group = Groups.groups(groupName)[groupName]
except KeyError, e:
turbogears.flash('Group Error: %s does not exist - %s' % (groupName, e))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
try:
p = Person.byUserName(userName)
if not p.cn:
raise KeyError, 'User %s, just not there' % userName
except KeyError, e:
turbogears.flash('User Error: %s does not exist - %s' % (userName, e))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
g = Groups.byGroupName(groupName, includeUnapproved=True)
# Apply user to a group (as in application)
if action == 'apply':
try:
Groups.apply(groupName, userName)
except ldap.ALREADY_EXISTS:
turbogears.flash('%s Already in group!' % p.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
else:
turbogears.flash('%s Applied!' % p.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
# Some error checking for the sponsors
if g[userName].fedoraRoleType.lower() == 'administrator' and g[sponsor].fedoraRoleType.lower() == 'sponsor':
raise ValueError, 'Sponsors cannot alter administrators. End of story'
try:
userGroup = Groups.byGroupName(groupName)[userName]
except KeyError:
# User not already in the group (happens when users apply for a group)
userGroup = UserGroup()
pass
# Remove user from a group
if action == 'remove':
try:
Groups.remove(group.cn, p.cn)
except TypeError:
turbogears.flash('%s could not be removed from %s!' % (p.cn, group.cn))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
else:
turbogears.flash('%s removed from %s!' % (p.cn, group.cn))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
return dict()
# Upgrade user in a group
elif action == 'upgrade':
if g[userName].fedoraRoleType.lower() == 'sponsor' and g[sponsor].fedoraRoleType.lower() == 'sponsor':
raise ValueError, 'Sponsors cannot admin other sponsors'
try:
p.upgrade(groupName)
except TypeError, e:
turbogears.flash('Cannot upgrade %s - %s!' % (p.cn, e))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
turbogears.flash('%s Upgraded!' % p.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
# Downgrade user in a group
elif action == 'downgrade':
if g[userName].fedoraRoleType.lower() == 'administrator' and g[sponsor].fedoraRoleType.lower() == 'sponsor':
raise ValueError, 'Sponsors cannot downgrade admins'
try:
p.downgrade(groupName)
except TypeError, e:
turbogears.flash('Cannot downgrade %s - %s!' % (p.cn, e))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
turbogears.flash('%s Downgraded!' % p.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
# Sponsor / Approve User
elif action == 'sponsor' or action == 'apply':
p.sponsor(groupName, sponsor)
turbogears.flash('%s has been sponsored!' % p.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
turbogears.flash('Invalid action: %s' % action)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
return dict()
@expose(template='fas.templates.inviteMember')
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def inviteMember(self, name=None, email=None, skills=None):
if name and email:
turbogears.flash('Invitation Sent to: "%s" <%s>' % (name, email))
if name or email:
turbogears.flash('Please provide both an email address and the persons name.')
return dict()
@expose(template='fas.templates.apply')
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def applyForGroup(self, groupName, action=None, requestField=None):
userName = turbogears.identity.current.user_name
group = Groups.groups(groupName)[groupName]
user = Person.byUserName(userName)
if action != 'Remove':
try:
Groups.apply(groupName, userName)
turbogears.flash('Application sent for %s' % user.cn)
except ldap.ALREADY_EXISTS, e:
turbogears.flash('Application Denied: %s' % e[0]['desc'])
turbogears.redirect('editGroup?groupName=%s' % group.cn)
if action == 'Remove' and group.fedoraGroupUserCanRemove == 'TRUE':
try:
Groups.remove(group.cn, user.cn)
except TypeError:
turbogears.flash('%s could not be removed from %s!' % (user.cn, group.cn))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
else:
turbogears.flash('%s removed from %s!' % (user.cn, group.cn))
turbogears.redirect('editGroup?groupName=%s' % group.cn)
else:
turbogears.flash('%s does not allow self removal' % group.cn)
turbogears.redirect('editGroup?groupName=%s' % group.cn)
return dict()
@expose(template='fas.templates.signUp')
def signUp(self):
if turbogears.identity.not_anonymous():
turbogears.flash('No need to sign up, You have an account!')
turbogears.redirect('editAccount')
return dict(form=newPersonForm)
@validate(form=newPersonForm)
@error_handler(signUp)
@expose(template='fas.templates.signUp')
def newAccountSubmit(self, cn, givenName, mail, telephoneNumber, postalAddress):
import turbomail
try:
Person.newPerson(cn.encode('utf8'), givenName.encode('utf8'), mail.encode('utf8'), telephoneNumber.encode('utf8'), postalAddress.encode('utf8'))
p = Person.byUserName(cn.encode('utf8'))
newpass = p.generatePassword()
message = turbomail.Message('accounts@fedoraproject.org', p.mail, 'Fedora Project Password Reset')
message.plain = "You have requested a password reset! Your new password is - %s \nPlease go to https://admin.fedoraproject.org/fas/ to change it" % newpass['pass']
turbomail.enqueue(message)
p.__setattr__('userPassword', newpass['hash'])
turbogears.flash('Your password has been emailed to you. Please log in with it and change your password')
turbogears.redirect('/')
except ldap.ALREADY_EXISTS:
turbogears.flash('%s Already Exists, Please pick a different name' % cn)
turbogears.redirect('signUp')
return dict()
@expose(format="json")
def search(self, userName=None, groupName=None):
people = Person.users('%s*' % userName)
return dict(people=
filter(lambda item: userName in item.lower(), people))
@expose(format="json")
def help(self, helpID='Unknown'):
messages = {
'Unknown' : ''' Unknown: If you know what help should be here, please email accounts@fedoraproject.org and tell them.''',
'postalAddress' : ''' Postal Address: Your local mailing address. It could be a work address or a home address.''',
'cn' : ''' Account Name: A unique identifier for each user. This is your 'username' for many parts of fedora. This will also be your @fedoraproject.org email alias.''',
'givenName' : ''' Real Name: This is your full name, often Firstname Lastname.''',
'mail' : ''' Email Address: This is your primary email address. Notifications, aliases, password resets all get sent to this address. Other email addresses can be added (like bugzilla address)''',
'fedoraPersonBugzillaMail' : ''' Bugzilla Email: For most this is the same address as as their primary email address.''',
'fedoraPersonIrcNick' : ''' IRC Nick: Many fedora developers can be found on freenode.net. Make sure your nick is registered so no one else takes it. After you have registered, let the rest of fedora know what your nick is.''',
'fedoraPersonKeyId' : ''' PGP Key: PGP key's are required to verify your identity to others and to encrypt messages. It is required in order to sign the CLA and, as such, is required to be a contributor. In order to create and upload your key please see our howto at: <a href='http://fedoraproject.org/wiki/DocsProject/UsingGpg/CreatingKeys'>http://fedoraproject.org/wiki/DocsProject/UsingGpg/CreatingKeys</a> ''',
'telephoneNumber' : ''' Telephone Number: Please include a country code if outside of the united states. ''',
'description' : ''' Description: Just a brief comment on yourself. Could include your website or blog. ''',
'password' : ''' Password: Used to access fedora resources. Resources that don't require your password may require ssh keys ''',
'accountStatus' : ''' Account Status: Some accounts may be disabled because of misconduct or password expiration. If your account is not active and you are not sure why, please contact <a href='mailto:accounts@fedoraproject.org>accounts@fedoraproject.org</a> or join #fedora-admin on <a href='http://irc.freenode.net/'>irc.freenode.net</a> ''',
'cla' : ''' Contributor License Agreement: This agreement is required in order to be a Fedora contributor. The CLA can be found at: <a href='http://fedoraproject.org/wiki/Legal/Licenses/CLA'>http://fedoraproject.org/wiki/Legal/Licenses/CLA</a> ''',
'inviteToGroup' : ''' This will add a user to the following group. They will initially be unapproved, just as if they had applied themselves. An email notification will be sent. '''
}
try:
messages[helpID]
except KeyError:
helpID='Unknown'
return dict(help=messages[helpID])
#filter(lambda item: userName in item.lower(), people))
@expose(template='fas.templates.invite')
@exception_handler(errorMessage,rules="isinstance(tg_exceptions,ValueError)")
@identity.require(identity.not_anonymous())
def invite(self, target=None):
import turbomail
user = Person.byUserName(turbogears.identity.current.user_name)
if target:
message = turbomail.Message(user.mail, target, 'Come join The Fedora Project!')
# message.plain = "Please come join the fedora project! Someone thinks your skills and abilities may be able to help our project. If your interested please go to http://fedoraproject.org/wiki/HelpWanted"
message.plain = "%s <%s> has invited you to join the Fedora \
Project! We are a community of users and developers who produce a \
complete operating system from entirely free and open source software \
(FOSS). %s thinks that you have knowledge and skills \
that make you a great fit for the Fedora community, and that you might \
be interested in contributing. \n\
\n\
How could you team up with the Fedora community to use and develop your \
skills? Check out http://fedoraproject.org/wiki/Join for some ideas. \
Our community is more than just software developers -- we also have a \
place for you whether you're an artist, a web site builder, a writer, or \
a people person. You'll grow and learn as you work on a team with other \
very smart and talented people. \n\
\n\
Fedora and FOSS are changing the world -- come be a part of it!" % (user.givenName, user.mail, user.givenName)
turbomail.enqueue(message)
turbogears.flash('Message sent to: %s' % target)
return dict(target=target, user=user)
def relativeUser(realUser, sudoUser):
''' Takes user and sees if they are allow to sudo for remote group'''
p = Person.byUserName('realUser')

BIN
fas/fas/controllers.pyc Normal file

Binary file not shown.

440
fas/fas/fasLDAP.py Normal file
View file

@ -0,0 +1,440 @@
#!/usr/bin/python
import ldap
class Server:
def __init__(self, server='localhost', who='', password=''):
self.ldapConn = ldap.open(server)
self.ldapConn.simple_bind_s(who, password)
class Group:
''' Group abstraction class '''
def __init__(self, cn, fedoraGroupOwner, fedoraGroupType, fedoraGroupNeedsSponsor, fedoraGroupUserCanRemove, fedoraGroupJoinMsg):
self.cn = cn
self.fedoraGroupOwner = fedoraGroupOwner
self.fedoraGroupType = fedoraGroupType
self.fedoraGroupNeedsSponsor = fedoraGroupNeedsSponsor
self.fedoraGroupUserCanRemove = fedoraGroupUserCanRemove
self.fedoraGroupJoinMsg = fedoraGroupJoinMsg
class UserGroup:
''' Individual User->Group abstraction class '''
def __init__(self, fedoraRoleApprovalDate=None, fedoraRoleSponsor=None, cn=None, fedoraRoleCreationDate=None, objectClass=None, fedoraRoleType=None, fedoraRoleStatus='Not a Member', fedoraRoleDomain=None):
self.fedoraRoleApprovalDate = fedoraRoleApprovalDate
self.fedoraRoleSponsor = fedoraRoleSponsor
self.cn = cn
self.fedoraRoleCreationDate = fedoraRoleCreationDate
self.objectClass = objectClass
self.fedoraRoleType = fedoraRoleType
self.fedoraRoleStatus = fedoraRoleStatus
self.fedoraRoleDomain = fedoraRoleDomain
class Groups:
''' Class contains group information '''
__userName = None
@classmethod
def byUserName(self, cn, includeUnapproved=None, unapprovedOnly=None):
''' Return list of groups a certain user is in. Excludes all non-approved groups'''
server = Server()
groups = {}
if includeUnapproved:
filter = 'objectClass=FedoraRole'
elif unapprovedOnly:
filter = '(&(!(fedoraRoleStatus=approved)) (objectClass=fedoraRole))'
else:
filter = '(&(fedoraRoleStatus=approved)(objectClass=FedoraRole))'
base = 'ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % cn
try:
groupsDict = search(base, filter)
except ldap.NO_SUCH_OBJECT:
return dict()
if not groupsDict:
groupsDict = []
for group in groupsDict:
cn = group[0][1]['cn'][0]
groups[cn] = UserGroup(
fedoraRoleApprovalDate = group[0][1]['fedoraRoleApprovalDate'][0],
fedoraRoleSponsor = group[0][1]['fedoraRoleSponsor'][0],
cn = group[0][1]['cn'][0],
fedoraRoleCreationDate = group[0][1]['fedoraRoleCreationDate'][0],
objectClass = group[0][1]['objectClass'][0],
fedoraRoleType = group[0][1]['fedoraRoleType'][0],
fedoraRoleStatus = group[0][1]['fedoraRoleStatus'][0],
fedoraRoleDomain = group[0][1]['fedoraRoleDomain'][0]
)
self.__userName = cn
return groups
@classmethod
def groups(self, searchExpression='*', attributes=[]):
groups = {}
filter = 'cn=%s' % (searchExpression)
base = 'ou=FedoraGroups,dc=fedoraproject,dc=org'
groupsDict = search(base, filter, attributes)
if groupsDict:
for group in groupsDict:
name = group[0][1]['cn'][0]
print group
groups[name] = Group(
cn = group[0][1]['cn'][0],
fedoraGroupOwner = group[0][1]['fedoraGroupOwner'][0],
fedoraGroupType = group[0][1]['fedoraGroupType'][0],
fedoraGroupNeedsSponsor = group[0][1]['fedoraGroupNeedsSponsor'][0],
fedoraGroupUserCanRemove = group[0][1]['fedoraGroupUserCanRemove'][0],
fedoraGroupJoinMsg = group[0][1]['fedoraGroupJoinMsg'][0])
else:
return None
return groups
@classmethod
def remove(self, groupName, userName=None):
if not userName:
userName = self.__userName
print "userName: %s" % userName
try:
g = self.byUserName(userName, includeUnapproved=True)[groupName]
except:
raise TypeError, 'User not in group %s' % groupName
try:
delete('cn=%s+fedoraRoleType=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (g.cn, g.fedoraRoleType, userName))
except ldap.NO_SUCH_OBJECT:
delete('cn=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (g.cn, userName))
except:
raise TypeError, 'Could Not delete %s from %s' % (userName, g.cn)
@classmethod
def apply(self, groupName, userName=None):
''' Apply for a group '''
import datetime
if not userName:
userName = self.__userName
if groupName in self.byUserName(userName):
# Probably shouldn't be 'TypeError'
raise TypeError, 'Already in that group'
try:
self.byGroupName(groupName)
except TypeError:
raise TypeError, 'Group "%s" does not exist' % groupName
dt = datetime.datetime.now()
now = '%.2i-%.2i-%.2i %.2i:%.2i:%.2i.%.2i' % (dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.microsecond)
attributes = { 'cn' : groupName.encode('utf8'),
'fedoraRoleApprovaldate' : 'NotApproved',
'fedoraRoleCreationDate' : now,
'fedoraRoleDomain' : 'None',
'fedoraRoleSponsor' : 'None',
'fedoraRoleStatus' : 'unapproved',
'fedoraRoleType' : 'user',
'objectClass' : ('fedoraRole')}
add('cn=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (groupName, userName), attributes)
@classmethod
def byGroupName(cls, cn, includeUnapproved=None, unapprovedOnly=None):
self = cls()
server = Server()
users = {}
if includeUnapproved:
filter = 'cn=%s' % cn
elif unapprovedOnly:
filter = '(&(cn=%s) (objectClass=fedoraRole) (!(fedoraRoleStatus=approved)))' % cn
else:
filter = '(&(cn=%s) (objectClass=fedoraRole) (fedoraRoleStatus=approved))' % cn
base = 'ou=People,dc=fedoraproject,dc=org'
self.__attributes = ['cn']
attributes = ['cn']
usersDict = search(base, filter)
for user in usersDict:
userName = user[0][0].split(',')[2].split('=')[1]
users[userName] = UserGroup(
fedoraRoleApprovalDate = user[0][1]['fedoraRoleApprovalDate'][0],
fedoraRoleSponsor = user[0][1]['fedoraRoleSponsor'][0],
cn = user[0][1]['cn'][0],
fedoraRoleCreationDate = user[0][1]['fedoraRoleCreationDate'][0],
objectClass = user[0][1]['objectClass'][0],
fedoraRoleType = user[0][1]['fedoraRoleType'][0],
fedoraRoleStatus = user[0][1]['fedoraRoleStatus'][0],
fedoraRoleDomain = user[0][1]['fedoraRoleDomain'][0]
)
return users
class Person:
''' information and attributes about users '''
__base = 'ou=People,dc=fedoraproject,dc=org'
__server = Server()
__filter = ''
__cn = ''
@classmethod
def newPerson(self, cn, givenName, mail, telephoneNumber, postalAddress):
import datetime
dt = datetime.datetime.now()
now = '%.2i-%.2i-%.2i %.2i:%.2i:%.2i.%.2i' % (dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.microsecond)
attributes = { 'cn' : cn,
'objectClass' : ('fedoraPerson', 'inetOrgPerson', 'organizationalPerson', 'person', 'top'),
'displayName' : cn,
'sn' : cn,
'cn' : cn,
'fedoraPersonSshKey' : '',
'facsimileTelephoneNumber' : '',
'fedoraPersonApprovalStatus' : 'approved',
'givenName' : givenName,
'mail' : mail,
'fedoraPersonKeyId' : '',
'description' : '',
'fedoraPersonCreationDate' : now,
'telephoneNumber' : telephoneNumber,
'fedoraPersonBugzillaMail' : mail,
'postalAddress' : postalAddress,
'fedoraPersonIrcNick' : '',
'userPassword' : 'Disabled'
}
add('cn=%s,%s' % (cn, self.__base), attributes)
attributes = {
'objectClass' : ('organizationalUnit', 'top'),
'ou' : 'Roles'
}
add('ou=Roles,cn=%s,%s' % (cn, self.__base), attributes)
return 0
def __getattr__(self, attr):
if attr.startswith('_'):
print 'GET %s=%s' % (attr, self.__getattr__(attr))
if attr == '__filter':
return self.__filter
if attr == 'userName':
return self.__getattr__('cn')
try:
attributes = []
attributes.append(attr)
return search(self.__base, self.__filter, attributes)[0][0][1][attr][0]
except:
# Should probably raise here.
return None
def __setattr__(self, attr, value):
if attr.startswith('_'):
#return setattr(self.__class__, attr, value)
self.__dict__[attr] = value
return
base = 'cn=%s,ou=People,dc=fedoraproject,dc=org' % self.__getattr__('cn')
if self.__getattr__(attr):
modify(base, attr, value, self.__getattr__(attr))
else:
try:
modify(base, attr, value)
except:
modify(base, attr, value, self.__getattr__(attr))
@classmethod
def users(self, searchExpression='*', findAttr='cn'):
''' Returns a list of users '''
users = []
filter = '(&(objectClass=top)(%s=%s))' % (findAttr, searchExpression)
attributes = ['cn']
usersDict = search(self.__base, filter, attributes)
if usersDict:
for user in usersDict:
users.append(user[0][1]['cn'][0])
else:
return None
return users
@classmethod
def byFilter(cls, filter):
''' Returns only the first result in the search '''
self = cls()
self.__filter = filter
return self
@classmethod
def byUserName(self, cn):
'''Wrapper for byFilter - search by cn'''
return self.byFilter('cn=%s' % cn)
@classmethod
def auth(self, who, password, ldapServer=None):
''' Basic Authentication Module '''
if not ldapServer:
s = Server()
ldapServer = s.ldapConn
who = 'cn=%s,ou=People,dc=fedoraproject,dc=org' % who
ldapServer.simple_bind_s(who, password)
def upgrade(self, group):
base = 'cn=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (group, self.cn)
g = Groups.byGroupName(group, includeUnapproved=True)[self.cn]
if not g.fedoraRoleStatus.lower() == 'approved':
'''User not approved or sponsored'''
raise TypeError, 'User is not approved'
if g.fedoraRoleType.lower() == 'administrator':
raise TypeError, 'User cannot be upgraded beyond administrator'
elif g.fedoraRoleType.lower() == 'sponsor':
modify(base, 'fedoraRoleType', 'administrator', g.fedoraRoleType)
elif g.fedoraRoleType.lower() == 'user':
modify(base, 'fedoraRoleType', 'sponsor', g.fedoraRoleType)
def downgrade(self, group):
base = 'cn=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (group, self.cn)
g = Groups.byGroupName(group, includeUnapproved=True)[self.cn]
if not g.fedoraRoleStatus.lower() == 'approved':
'''User not approved or sponsored'''
raise TypeError, 'User is not approved'
if g.fedoraRoleType.lower() == 'user':
raise TypeError, 'User cannot be downgraded below user, did you mean remove?'
elif g.fedoraRoleType.lower() == 'sponsor':
modify(base, 'fedoraRoleType', 'user', g.fedoraRoleType)
elif g.fedoraRoleType.lower() == 'administrator':
modify(base, 'fedoraRoleType', 'sponsor', g.fedoraRoleType)
def sponsor(self, groupName, sponsor):
import datetime
base = 'cn=%s,ou=Roles,cn=%s,ou=People,dc=fedoraproject,dc=org' % (groupName, self.cn)
g = Groups.byGroupName(groupName, includeUnapproved=True)[self.cn]
group = Groups.groups(groupName)[groupName]
print "SPONSORING: %s from %s for %s - %s" % (self.cn, sponsor, groupName, base)
dt = datetime.datetime.now()
now = '%.2i-%.2i-%.2i %.2i:%.2i:%.2i.%.2i' % (dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.microsecond)
modify(base, 'fedoraRoleApprovalDate', now)
if group.fedoraGroupNeedsSponsor.lower() == 'true':
modify(base, 'fedoraRoleSponsor', sponsor)
else:
modify(base, 'fedoraRoleSponsor', 'None')
modify(base, 'fedoraRoleStatus', 'approved')
def generatePassword(self,password=None,length=14,salt=''):
from random import Random
import sha
import sha
from base64 import b64encode
import sys
secret = {} # contains both hash and password
if not password:
rand = Random()
password = ''
# Exclude 0,O and l,1
righthand = '23456qwertasdfgzxcvbQWERTASDFGZXCVB'
lefthand = '789yuiophjknmYUIPHJKLNM'
for i in range(length):
if i%2:
password = password + rand.choice(lefthand)
else:
password = password + rand.choice(righthand)
ctx = sha.new(password)
ctx.update(salt)
secret['hash'] = "{SSHA}%s" % b64encode(ctx.digest() + salt)
secret['pass'] = password
return secret
class UserAccount:
def __init__(self):
self.realName = ''
self.userName = ''
self.primaryEmail = ''
self.groups = []
def delete(base, ldapServer=None):
''' Delete target base '''
if not ldapServer:
s = Server()
ldapServer = s.ldapConn
ldapServer.simple_bind_s('cn=directory manager', 'test')
print "Deleteing %s " % base
ldapServer.delete_s(base)
def add(base, attributes, ldapServer=None):
''' Add a new record to LDAP instance '''
if not ldapServer:
s = Server()
ldapServer = s.ldapConn
attributes=[ (k,v) for k,v in attributes.items() ]
ldapServer.simple_bind_s('cn=directory manager', 'test')
ldapServer.add_s(base, attributes)
def modify(base, attribute, new, old=None, ldapServer=None):
''' Modify an attribute, requires write access '''
if old == new:
return None
if not ldapServer:
s = Server()
ldapServer = s.ldapConn
from ldap import modlist
ldapServer.simple_bind_s('cn=directory manager', 'test')
if old == None:
old = 'None'
if old == new:
return None
o = { attribute : old }
n = { attribute : new }
ldif = modlist.modifyModlist(o, n)
#commit
ldapServer.modify_s(base, ldif)
ldapServer.unbind_s()
def search(base, filter, attributes=None, ldapServer=None):
if not ldapServer:
s = Server()
ldapServer = s.ldapConn
scope = ldap.SCOPE_SUBTREE
count = 0
timeout = 2
ldapServer.simple_bind_s('cn=directory manager', 'test')
result_set = []
try:
result_id = ldapServer.search(base, scope, filter, attributes)
while 1:
result_type, result_data = ldapServer.result(result_id, timeout)
if (result_data == []):
break
else:
if result_type == ldap.RES_SEARCH_ENTRY:
result_set.append(result_data)
if len(result_set) == 0:
print "No results."
return
except ldap.LDAPError, e:
print "Crap: %s" % e
raise
ldapServer.unbind_s()
return result_set

BIN
fas/fas/fasLDAP.pyc Normal file

Binary file not shown.

17
fas/fas/feeds.py Normal file
View file

@ -0,0 +1,17 @@
import urllib
from xml.dom import minidom
class Koji:
def __init__(self, userName, url='http://publictest8/koji/recentbuilds?user='):
buildFeed = minidom.parse(urllib.urlopen(url + userName))
try:
self.userLink = buildFeed.getElementsByTagName('link')[0].childNodes[0].data
self.builds = {}
for build in buildFeed.getElementsByTagName('item'):
link = build.getElementsByTagName('link')[0].childNodes[0].data
self.builds[link] = {}
self.builds[link]['title'] = build.getElementsByTagName('title')[0].childNodes[0].data
self.builds[link]['pubDate'] = build.getElementsByTagName('pubDate')[0].childNodes[0].data
except IndexError:
return

BIN
fas/fas/feeds.pyc Normal file

Binary file not shown.

33
fas/fas/json.py Normal file
View file

@ -0,0 +1,33 @@
# A JSON-based API(view) for your app.
# Most rules would look like:
# @jsonify.when("isinstance(obj, YourClass)")
# def jsonify_yourclass(obj):
# return [obj.val1, obj.val2]
# @jsonify can convert your objects to following types:
# lists, dicts, numbers and strings
from turbojson.jsonify import jsonify
from turbojson.jsonify import jsonify_sqlobject
from fas.model import User, Group, Permission
@jsonify.when('isinstance(obj, Group)')
def jsonify_group(obj):
result = jsonify_sqlobject( obj )
result["users"] = [u.user_name for u in obj.users]
result["permissions"] = [p.permission_name for p in obj.permissions]
return result
@jsonify.when('isinstance(obj, User)')
def jsonify_user(obj):
result = jsonify_sqlobject( obj )
del result['password']
result["groups"] = [g.group_name for g in obj.groups]
result["permissions"] = [p.permission_name for p in obj.permissions]
return result
@jsonify.when('isinstance(obj, Permission)')
def jsonify_permission(obj):
result = jsonify_sqlobject( obj )
result["groups"] = [g.group_name for g in obj.groups]
return result

33
fas/fas/model.py Normal file
View file

@ -0,0 +1,33 @@
from datetime import datetime
from turbogears.database import PackageHub
from sqlobject import *
from turbogears import identity
hub = PackageHub("fas")
__connection__ = hub
# class YourDataClass(SQLObject):
# pass
# identity models.
class Visit(SQLObject):
class sqlmeta:
table = "visit"
visit_key = StringCol(length=40, alternateID=True,
alternateMethodName="by_visit_key")
created = DateTimeCol(default=datetime.now)
expiry = DateTimeCol()
def lookup_visit(cls, visit_key):
try:
return cls.by_visit_key(visit_key)
except SQLObjectNotFound:
return None
lookup_visit = classmethod(lookup_visit)
class VisitIdentity(SQLObject):
visit_key = StringCol(length=40, alternateID=True,
alternateMethodName="by_visit_key")
user_id = IntCol()

14
fas/fas/release.py Normal file
View file

@ -0,0 +1,14 @@
# Release information about fas
version = "1.0"
# description = "Your plan to rule the world"
# long_description = "More description about your plan"
# author = "Your Name Here"
# email = "YourEmail@YourDomain"
# copyright = "Vintage 2006 - a good year indeed"
# if it's open source, you might want to specify these
# url = "http://yourcool.site/"
# download_url = "http://yourcool.site/download"
# license = "MIT"

View file

@ -0,0 +1,28 @@
h1 {
font-size: 2em;
color: #4B4545;
text-align: center;
}
.draggable
{
color: white;
cursor: move;
font-size: 20px;
// height: 100px;
// line-height: 100px;
position: absolute;
text-align: center;
top: 200px;
width: 100px;
}
.blue { background: blue; }
.green { background: green; }
.red { background: red; }
.white
{
background: white;
border: 1px solid black;
color: black;
}

354
fas/fas/static/css/fas.css Normal file
View file

@ -0,0 +1,354 @@
*
{
margin: 0;
padding: 0;
}
body
{
font-size: 76%;
}
a
{
text-decoration: none;
}
#wrapper
{
font: normal 2ex/1.5 sans-serif;
}
#head
{
overflow: hidden;
margin-top: 35px;
height: 70px;
line-height: 70px;
background: url(images/head.png) 0 0 repeat-x;
}
#head h1
{
width: 250px;
float: left;
text-indent: -9999px;
overflow: hidden;
background: url(images/logo.png) 1ex 50% no-repeat;
}
#searchbox
{
width: 36ex;
float: right;
text-align: right;
margin-right: 2ex;
}
#searchbox label
{
display: none;
}
#searchbox input
{
display: inline;
border: 1px solid #CCCCCC;
}
#searchbox #q
{
width: 20ex;
}
#topnav
{
height: 30px;
line-height: 30px;
background: url(images/topnav.png) 0 0 repeat-x;
font-size: 1.6ex;
}
#topnav ul
{
list-style: none;
text-align: center;
}
#topnav ul li
{
display: inline;
background: url(images/topnav-separator.png) 0 50% no-repeat;
padding-left: 3px;
}
#topnav ul li.first
{
background: none;
}
#topnav a
{
color: #445566;
margin: 0 2ex;
}
#topnav a:hover
{
color: #000000;
}
#infobar
{
position: absolute;
top: 0;
left: 0;
right: 0;
height: 35px;
line-height: 35px;
background: url(images/infobar.png) 0 0 repeat-x;
font-size: 1.6ex;
}
#authstatus
{
width: 40ex;
float: left;
color: #FFFFFF;
padding-left: 1.5ex;
}
#authstatus strong
{
color: #DED6A1;
}
#control
{
width: 40ex;
float: right;
margin-right: 1ex;
}
#control ul
{
list-style: none;
text-align: right;
}
#control ul li
{
display: inline;
background: url(images/control-separator.png) 0 50% no-repeat;
}
#control a
{
color: #DED6A1;
margin: 0 1.5ex;
}
#main
{
background: url(images/shadow.png) 0 0 repeat-x;
}
#sidebar
{
width: 22ex;
float: left;
background: #335F9D url(images/sidebar.png) 0 0 repeat-x;
border: 1px solid #112233;
}
#sidebar ul
{
list-style: none;
}
#sidebar li
{
border-top: 1px solid #CCCCCC;
}
#sidebar li.first
{
border-top: none;
}
#sidebar a
{
display: block;
text-align: center;
color: #FFFFFF;
padding: 0.5ex 0;
}
#sidebar a:hover
{
background: #082C59;
}
#content
{
margin-left: 22ex;
padding: 2ex 4ex;
}
#content h2
{
/* header icon */
}
#content a
{
color: #0C6ED0;
}
.userbox
{
}
.userbox dt
{
width: 23ex;
float: left;
text-align: right;
}
.userbox dd
{
margin-left: 25ex;
}
.account
{
padding-left: 30px;
background: url(images/account.png) 0 68% no-repeat;
}
.approved
{
padding-left: 20px;
background: url(images/approved.png) 0 68% no-repeat;
}
.unapproved
{
padding-left: 20px;
background: url(images/unapproved.png) 0 68% no-repeat;
}
.attn
{
padding-left: 20px;
background: url(images/attn.png) 0 68% no-repeat;
}
.roleslist
{
list-style: none;
}
.roleslist li
{
margin-left: 0.5ex;
}
.actions
{
margin-top: 1.5ex;
list-style: none;
}
.actions li
{
display: inline;
}
#rolespanel
{
list-style: none;
}
#rolespanel li.role
{
border-top: 2px solid #EEEEEE;
margin-top: 1ex;
padding-top: 1ex;
padding-left: 22px;
background: url(images/arrow.png) 0 1.6ex no-repeat;
}
#rolespanel h4
{
display: inline;
}
#rolespanel dt
{
width: 10ex;
float: left;
text-align: right;
margin-bottom: 1ex;
}
#rolespanel dd
{
margin-left: 12ex;
margin-bottom: 1ex;
}
#rolespanel .tools, #rolespanel .queue
{
list-style: none;
}
#rolespanel .tools li
{
padding-left: 22px;
background: url(images/tools.png) 0 50% no-repeat;
}
#rolespanel .queue li
{
padding-left: 22px;
background: url(images/queue.png) 0 50% no-repeat;
}
#footer
{
font-size: 1.6ex;
clear: both;
text-align: center;
padding: 15px 0 2.5ex;
background: url(images/footer-top.png) 0 0 repeat-x;
}
#footlinks
{
padding-top: 3px;
padding-bottom: 18px;
background: #EEEEEE url(images/footer-bottom.png) 0 100% repeat-x;
list-style: none;
}
#footlinks li
{
display: inline;
border-left: 1px solid #CCCCCC;
padding-left: 1px;
}
#footlinks li.first
{
padding-left: 0;
border-left: none;
}
#footlinks a
{
margin: 0 2ex;
color: #3465A4;
}

View file

@ -0,0 +1,66 @@
h1 {
font-size: 2em;
color: #4B4545;
text-align: center;
}
table.datagrid {
/* width: 100%; */
border-collapse: collapse;
}
table.datagrid thead th {
text-align: left;
background-color: #4B4545;
background-repeat: no-repeat;
background-position: right center;
color: white;
font-weight: bold;
padding: .3em .7em;
font-size: .9em;
padding-right: 5px;
background-repeat: no-repeat;
background-position: 95% right;
}
table.datagrid thead th a {
color: white;
text-decoration: none;
font-size: 1.0em;
background-repeat: no-repeat;
background-position: center right;
padding-right: 15px;
}
table.datagrid thead th.over {
background-color: #746B6B;
cursor: pointer;
}
table.datagrid tbody th {
font-weight: bold;
}
table.datagrid tbody td, table.datagrid tbody th {
text-align: left;
padding: .3em .7em;
border-bottom: 1px solid #eee;
}
table.datagrid tbody tr.alternate td, table.datagrid tbody tr.alternate th {
background-color: #f1f1f1;
}
table.datagrid tfoot td, table.datagrid tfoot th {
background-color: #FFFEE3;
color: #4B4545;
padding: .5em;
font-weight: bold;
border-top: 2px solid #4B4545;
}
table.datagrid tfoot th { text-align: left; }
table.datagrid tfoot td { }
.invisible { display: none; }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,381 @@
*
{
margin: 0;
padding: 0;
}
body
{
font-size: 76%;
}
a
{
text-decoration: none;
}
#wrapper
{
font: normal 2ex/1.5 sans-serif;
}
#head
{
overflow: hidden;
margin-top: 35px;
height: 70px;
line-height: 70px;
background: url(/fas/static/images/head.png) 0 0 repeat-x;
}
#head h1
{
width: 250px;
float: left;
text-indent: -9999px;
overflow: hidden;
background: url(/fas/static/images/logo.png) 1ex 50% no-repeat;
}
#searchbox
{
width: 36ex;
float: right;
text-align: right;
margin-right: 2ex;
}
#searchbox label
{
display: none;
}
#searchbox input
{
display: inline;
border: 1px solid #CCCCCC;
}
#searchbox #q
{
width: 20ex;
}
#topnav
{
height: 30px;
line-height: 30px;
background: url(/fas/static/images/topnav.png) 0 0 repeat-x;
font-size: 1.6ex;
}
#topnav ul
{
list-style: none;
text-align: center;
}
#topnav ul li
{
display: inline;
background: url(/fas/static/images/topnav-separator.png) 0 50% no-repeat;
padding-left: 3px;
}
#topnav ul li.first
{
background: none;
}
#topnav a
{
color: #445566;
margin: 0 2ex;
}
#topnav a:hover
{
color: #000000;
}
#infobar
{
position: absolute;
top: 0;
left: 0;
right: 0;
height: 35px;
line-height: 35px;
background: url(/fas/static/images/infobar.png) 0 0 repeat-x;
font-size: 1.6ex;
}
#authstatus
{
width: 40ex;
float: left;
color: #FFFFFF;
padding-left: 1.5ex;
}
#authstatus strong
{
color: #DED6A1;
}
#control
{
width: 40ex;
float: right;
margin-right: 1ex;
}
#control ul
{
list-style: none;
text-align: right;
}
#control ul li
{
display: inline;
background: url(/fas/static/images/control-separator.png) 0 50% no-repeat;
}
#control a
{
color: #DED6A1;
margin: 0 1.5ex;
}
#main
{
background: url(/fas/static/images/shadow.png) 0 0 repeat-x;
}
#sidebar
{
width: 22ex;
float: left;
background: #335F9D url(/fas/static/images/sidebar.png) 0 0 repeat-x;
border: 1px solid #112233;
}
#sidebar ul
{
list-style: none;
}
#sidebar li
{
border-top: 1px solid #CCCCCC;
}
#sidebar li.first
{
border-top: none;
}
#sidebar a
{
display: block;
text-align: center;
color: #FFFFFF;
padding: 0.5ex 0;
}
#sidebar a:hover
{
background: #082C59;
}
#content
{
margin-left: 22ex;
padding: 2ex 4ex;
}
#content h2
{
/* header icon */
}
#content a
{
color: #0C6ED0;
}
.userbox
{
}
.userbox dt
{
width: 23ex;
float: left;
text-align: right;
}
.userbox dd
{
margin-left: 25ex;
}
.account
{
padding-left: 30px;
background: url(/fas/static/images/account.png) 0 68% no-repeat;
}
.approved
{
padding-left: 20px;
background: url(/fas/static/images/approved.png) 0 68% no-repeat;
}
.unapproved
{
padding-left: 20px;
background: url(/fas/static/images/unapproved.png) 0 68% no-repeat;
}
.attn
{
padding-left: 20px;
background: url(/fas/static/images/attn.png) 0 68% no-repeat;
}
.roleslist
{
list-style: none;
}
.roleslist li
{
margin-left: 0.5ex;
}
.actions
{
margin-top: 1.5ex;
list-style: none;
}
.actions li
{
display: inline;
}
#rolespanel
{
list-style: none;
}
#rolespanel li.role
{
border-top: 2px solid #EEEEEE;
margin-top: 1ex;
padding-top: 1ex;
padding-left: 22px;
background: url(/fas/static/images/arrow.png) 0 1.6ex no-repeat;
}
#rolespanel h4
{
display: inline;
}
#rolespanel dt
{
width: 10ex;
float: left;
text-align: right;
margin-bottom: 1ex;
}
#rolespanel dd
{
margin-left: 12ex;
margin-bottom: 1ex;
}
#rolespanel .tools, #rolespanel .queue
{
list-style: none;
}
#rolespanel .tools li
{
padding-left: 22px;
background: url(/fas/static/images/tools.png) 0 50% no-repeat;
}
#rolespanel .queue li
{
padding-left: 22px;
background: url(/fas/static/images/queue.png) 0 50% no-repeat;
}
#footer
{
font-size: 1.6ex;
clear: both;
text-align: center;
padding: 15px 0 2.5ex;
background: url(/fas/static/images/footer-top.png) 0 0 repeat-x;
}
#footlinks
{
padding-top: 3px;
padding-bottom: 18px;
background: #EEEEEE url(/fas/static/images/footer-bottom.png) 0 100% repeat-x;
list-style: none;
}
#footlinks li
{
display: inline;
border-left: 1px solid #CCCCCC;
padding-left: 1px;
}
#footlinks li.first
{
padding-left: 0;
border-left: none;
}
#footlinks a
{
margin: 0 2ex;
color: #3465A4;
}
.flash
{
background: #DEE6B1 url(/fas/static/images/success.png) 10px 50% no-repeat;
border: 1px solid #CCBBAA;
padding: 1.5ex 15px 1.5ex 43px;
margin: 1ex 0;
}
.flash
{
float: left;
background: #DEE6B1 url(/fas/static/images/success.png) 10px 50% no-repeat;
height: 50px;
line-height: 50px;
border: 1px solid #998877;
padding: 0 15px 0 43px;
margin-top: 9px;
}
.help
{
background: #DEE6B1 url(/fas/static/images/help.png) 10px 50% no-repeat;
border: 1px solid #CCBBAA;
padding: 1.5ex 15px 1.5ex 65px;
margin: 1ex 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,774 @@
/***
MochiKit.DragAndDrop 1.4
See <http://mochikit.com/> for documentation, downloads, license, etc.
Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
Mochi-ized By Thomas Herve (_firstname_@nimail.org)
***/
if (typeof(dojo) != 'undefined') {
dojo.provide('MochiKit.DragAndDrop');
dojo.require('MochiKit.Base');
dojo.require('MochiKit.DOM');
dojo.require('MochiKit.Iter');
dojo.require('MochiKit.Visual');
dojo.require('MochiKit.Signal');
}
if (typeof(JSAN) != 'undefined') {
JSAN.use("MochiKit.Base", []);
JSAN.use("MochiKit.DOM", []);
JSAN.use("MochiKit.Visual", []);
JSAN.use("MochiKit.Iter", []);
JSAN.use("MochiKit.Signal", []);
}
try {
if (typeof(MochiKit.Base) == 'undefined' ||
typeof(MochiKit.DOM) == 'undefined' ||
typeof(MochiKit.Visual) == 'undefined' ||
typeof(MochiKit.Signal) == 'undefined' ||
typeof(MochiKit.Iter) == 'undefined') {
throw "";
}
} catch (e) {
throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM, MochiKit.Visual, MochiKit.Signal and MochiKit.Iter!";
}
if (typeof(MochiKit.DragAndDrop) == 'undefined') {
MochiKit.DragAndDrop = {};
}
MochiKit.DragAndDrop.NAME = 'MochiKit.DragAndDrop';
MochiKit.DragAndDrop.VERSION = '1.4';
MochiKit.DragAndDrop.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.DragAndDrop.toString = function () {
return this.__repr__();
};
MochiKit.DragAndDrop.EXPORT = [
"Droppable",
"Draggable"
];
MochiKit.DragAndDrop.EXPORT_OK = [
"Droppables",
"Draggables"
];
MochiKit.DragAndDrop.Droppables = {
/***
Manage all droppables. Shouldn't be used, use the Droppable object instead.
***/
drops: [],
remove: function (element) {
this.drops = MochiKit.Base.filter(function (d) {
return d.element != MochiKit.DOM.getElement(element)
}, this.drops);
},
register: function (drop) {
this.drops.push(drop);
},
unregister: function (drop) {
this.drops = MochiKit.Base.filter(function (d) {
return d != drop;
}, this.drops);
},
prepare: function (element) {
MochiKit.Base.map(function (drop) {
if (drop.isAccepted(element)) {
if (drop.options.activeclass) {
MochiKit.DOM.addElementClass(drop.element,
drop.options.activeclass);
}
drop.options.onactive(drop.element, element);
}
}, this.drops);
},
findDeepestChild: function (drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i) {
if (MochiKit.DOM.isParent(drops[i].element, deepest.element)) {
deepest = drops[i];
}
}
return deepest;
},
show: function (point, element) {
if (!this.drops.length) {
return;
}
var affected = [];
if (this.last_active) {
this.last_active.deactivate();
}
MochiKit.Iter.forEach(this.drops, function (drop) {
if (drop.isAffected(point, element)) {
affected.push(drop);
}
});
if (affected.length > 0) {
drop = this.findDeepestChild(affected);
MochiKit.Position.within(drop.element, point.page.x, point.page.y);
drop.options.onhover(element, drop.element,
MochiKit.Position.overlap(drop.options.overlap, drop.element));
drop.activate();
}
},
fire: function (event, element) {
if (!this.last_active) {
return;
}
MochiKit.Position.prepare();
if (this.last_active.isAffected(event.mouse(), element)) {
this.last_active.options.ondrop(element,
this.last_active.element, event);
}
},
reset: function (element) {
MochiKit.Base.map(function (drop) {
if (drop.options.activeclass) {
MochiKit.DOM.removeElementClass(drop.element,
drop.options.activeclass);
}
drop.options.ondesactive(drop.element, element);
}, this.drops);
if (this.last_active) {
this.last_active.deactivate();
}
}
};
MochiKit.DragAndDrop.Droppable = function (element, options) {
this.__init__(element, options);
};
MochiKit.DragAndDrop.Droppable.prototype = {
/***
A droppable object. Simple use is to create giving an element:
new MochiKit.DragAndDrop.Droppable('myelement');
Generally you'll want to define the 'ondrop' function and maybe the
'accept' option to filter draggables.
***/
__class__: MochiKit.DragAndDrop.Droppable,
__init__: function (element, /* optional */options) {
var d = MochiKit.DOM;
var b = MochiKit.Base;
this.element = d.getElement(element);
this.options = b.update({
greedy: true,
hoverclass: null,
activeclass: null,
hoverfunc: b.noop,
accept: null,
onactive: b.noop,
ondesactive: b.noop,
onhover: b.noop,
ondrop: b.noop,
containment: [],
tree: false
}, options || {});
// cache containers
this.options._containers = [];
b.map(MochiKit.Base.bind(function (c) {
this.options._containers.push(d.getElement(c));
}, this), this.options.containment);
d.makePositioned(this.element); // fix IE
MochiKit.DragAndDrop.Droppables.register(this);
},
isContained: function (element) {
if (this._containers) {
var containmentNode;
if (this.options.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return MochiKit.Iter.some(this._containers, function (c) {
return containmentNode == c;
});
} else {
return true;
}
},
isAccepted: function (element) {
return ((!this.options.accept) || MochiKit.Iter.some(
this.options.accept, function (c) {
return MochiKit.DOM.hasElementClass(element, c);
}));
},
isAffected: function (point, element) {
return ((this.element != element) &&
this.isContained(element) &&
this.isAccepted(element) &&
MochiKit.Position.within(this.element, point.page.x,
point.page.y));
},
deactivate: function () {
/***
A droppable is deactivate when a draggable has been over it and left.
***/
if (this.options.hoverclass) {
MochiKit.DOM.removeElementClass(this.element,
this.options.hoverclass);
}
this.options.hoverfunc(this.element, false);
MochiKit.DragAndDrop.Droppables.last_active = null;
},
activate: function () {
/***
A droppable is active when a draggable is over it.
***/
if (this.options.hoverclass) {
MochiKit.DOM.addElementClass(this.element, this.options.hoverclass);
}
this.options.hoverfunc(this.element, true);
MochiKit.DragAndDrop.Droppables.last_active = this;
},
destroy: function () {
/***
Delete this droppable.
***/
MochiKit.DragAndDrop.Droppables.unregister(this);
},
repr: function () {
return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
}
};
MochiKit.DragAndDrop.Draggables = {
/***
Manage draggables elements. Not intended to direct use.
***/
drags: [],
observers: [],
register: function (draggable) {
if (this.drags.length === 0) {
var conn = MochiKit.Signal.connect;
this.eventMouseUp = conn(document, 'onmouseup', this, this.endDrag);
this.eventMouseMove = conn(document, 'onmousemove', this,
this.updateDrag);
this.eventKeypress = conn(document, 'onkeypress', this,
this.keyPress);
}
this.drags.push(draggable);
},
unregister: function (draggable) {
this.drags = MochiKit.Base.filter(function (d) {
return d != draggable;
}, this.drags);
if (this.drags.length === 0) {
var disc = MochiKit.Signal.disconnect
disc(this.eventMouseUp);
disc(this.eventMouseMove);
disc(this.eventKeypress);
}
},
activate: function (draggable) {
// allows keypress events if window is not currently focused
// fails for Safari
window.focus();
this.activeDraggable = draggable;
},
deactivate: function () {
this.activeDraggable = null;
},
updateDrag: function (event) {
if (!this.activeDraggable) {
return;
}
var pointer = event.mouse();
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if (this._lastPointer && (MochiKit.Base.repr(this._lastPointer.page) ==
MochiKit.Base.repr(pointer.page))) {
return;
}
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function (event) {
if (!this.activeDraggable) {
return;
}
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function (event) {
if (this.activeDraggable) {
this.activeDraggable.keyPress(event);
}
},
addObserver: function (observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function (element) {
// element instead of observer fixes mem leaks
this.observers = MochiKit.Base.filter(function (o) {
return o.element != element;
}, this.observers);
this._cacheObserverCallbacks();
},
notify: function (eventName, draggable, event) {
// 'onStart', 'onEnd', 'onDrag'
if (this[eventName + 'Count'] > 0) {
MochiKit.Base.map(function (o) {
if (o[eventName]) {
o[eventName](eventName, draggable, event);
}
}, this.observers);
}
},
_cacheObserverCallbacks: function () {
var b = MochiKit.Base;
var self = MochiKit.DragAndDrop.Draggables;
b.map(function (eventName) {
self[eventName + 'Count'] = b.filter(function (o) {
return o[eventName];
}, self.observers).length;
}, ['onStart', 'onEnd', 'onDrag']);
}
};
MochiKit.DragAndDrop.Draggable = function (element, options) {
this.__init__(element, options);
};
MochiKit.DragAndDrop.Draggable.prototype = {
/***
A draggable object. Simple instantiate :
new MochiKit.DragAndDrop.Draggable('myelement');
***/
__class__ : MochiKit.DragAndDrop.Draggable,
__init__: function (element, /* optional */options) {
var v = MochiKit.Visual;
var b = MochiKit.Base;
options = b.update({
handle: false,
starteffect: function (innerelement) {
this._savedOpacity = MochiKit.DOM.getOpacity(innerelement) || 1.0;
new v.Opacity(innerelement, {duration:0.2, from:this._savedOpacity, to:0.7});
},
reverteffect: function (innerelement, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2) +
Math.abs(left_offset^2))*0.02;
return new v.Move(innerelement,
{x: -left_offset, y: -top_offset, duration: dur});
},
endeffect: function (innerelement) {
new v.Opacity(innerelement, {duration:0.2, from:0.7, to:this._savedOpacity});
},
onchange: b.noop,
zindex: 1000,
revert: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
// false, or xy or [x, y] or function (x, y){return [x, y];}
snap: false
}, options || {});
var d = MochiKit.DOM;
this.element = d.getElement(element);
if (options.handle && (typeof(options.handle) == 'string')) {
this.handle = d.getFirstElementByTagAndClassName(null,
options.handle, this.element);
}
if (!this.handle) {
this.handle = d.getElement(options.handle);
}
if (!this.handle) {
this.handle = this.element;
}
if (options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = d.getElement(options.scroll);
}
d.makePositioned(this.element); // fix IE
this.delta = this.currentDelta();
this.options = options;
this.dragging = false;
this.eventMouseDown = MochiKit.Signal.connect(this.handle,
'onmousedown', this, this.initDrag);
MochiKit.DragAndDrop.Draggables.register(this);
},
destroy: function () {
MochiKit.Signal.disconnect(this.eventMouseDown);
MochiKit.DragAndDrop.Draggables.unregister(this);
},
currentDelta: function () {
var s = MochiKit.DOM.getStyle;
return [
parseInt(s(this.element, 'left') || '0'),
parseInt(s(this.element, 'top') || '0')];
},
initDrag: function (event) {
if (!event.mouse().button.left) {
return;
}
// abort on form elements, fixes a Firefox issue
var src = event.target;
if (src.tagName && (
src.tagName == 'INPUT' || src.tagName == 'SELECT' ||
src.tagName == 'OPTION' || src.tagName == 'BUTTON' ||
src.tagName == 'TEXTAREA')) {
return;
}
if (this._revert) {
this._revert.cancel();
this._revert = null;
}
var pointer = event.mouse();
var pos = MochiKit.Position.cumulativeOffset(this.element);
this.offset = [pointer.page.x - pos.x, pointer.page.y - pos.y]
MochiKit.DragAndDrop.Draggables.activate(this);
event.stop();
},
startDrag: function (event) {
this.dragging = true;
if (this.options.selectclass) {
MochiKit.DOM.addElementClass(this.element,
this.options.selectclass);
}
if (this.options.zindex) {
this.originalZ = parseInt(MochiKit.DOM.getStyle(this.element,
'z-index') || '0');
this.element.style.zIndex = this.options.zindex;
}
if (this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this.ghostPosition = MochiKit.Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if (this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
MochiKit.DragAndDrop.Droppables.prepare(this.element);
MochiKit.DragAndDrop.Draggables.notify('onStart', this, event);
if (this.options.starteffect) {
this.options.starteffect(this.element);
}
},
updateDrag: function (event, pointer) {
if (!this.dragging) {
this.startDrag(event);
}
MochiKit.Position.prepare();
MochiKit.DragAndDrop.Droppables.show(pointer, this.element);
MochiKit.DragAndDrop.Draggables.notify('onDrag', this, event);
this.draw(pointer);
this.options.onchange(this);
if (this.options.scroll) {
this.stopScrolling();
var p, q;
if (this.options.scroll == window) {
var s = this._getWindowScroll(this.options.scroll);
p = new MochiKit.Style.Coordinates(s.left, s.top);
q = new MochiKit.Style.Coordinates(s.left + s.width,
s.top + s.height);
} else {
p = MochiKit.Position.page(this.options.scroll);
p.x += this.options.scroll.scrollLeft;
p.y += this.options.scroll.scrollTop;
q = new MochiKit.Style.Coordinates(p.x + this.options.scroll.offsetWidth,
p.y + this.options.scroll.offsetHeight);
}
var speed = [0, 0];
if (pointer.page.x > (q.x - this.options.scrollSensitivity)) {
speed[0] = pointer.page.x - (q.x - this.options.scrollSensitivity);
} else if (pointer.page.x < (p.x + this.options.scrollSensitivity)) {
speed[0] = pointer.page.x - (p.x + this.options.scrollSensitivity);
}
if (pointer.page.y > (q.y - this.options.scrollSensitivity)) {
speed[1] = pointer.page.y - (q.y - this.options.scrollSensitivity);
} else if (pointer.page.y < (p.y + this.options.scrollSensitivity)) {
speed[1] = pointer.page.y - (p.y + this.options.scrollSensitivity);
}
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if (MochiKit.Base.isSafari()) {
window.scrollBy(0, 0);
}
event.stop();
},
finishDrag: function (event, success) {
var dr = MochiKit.DragAndDrop;
this.dragging = false;
if (this.options.selectclass) {
MochiKit.DOM.removeElementClass(this.element,
this.options.selectclass);
}
if (this.options.ghosting) {
// XXX: from a user point of view, it would be better to remove
// the node only *after* the MochiKit.Visual.Move end when used
// with revert.
MochiKit.Position.relativize(this.element, this.ghostPosition);
MochiKit.DOM.removeElement(this._clone);
this._clone = null;
}
if (success) {
dr.Droppables.fire(event, this.element);
}
dr.Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if (revert && typeof(revert) == 'function') {
revert = revert(this.element);
}
var d = this.currentDelta();
if (revert && this.options.reverteffect) {
this._revert = this.options.reverteffect(this.element,
d[1] - this.delta[1], d[0] - this.delta[0]);
} else {
this.delta = d;
}
if (this.options.zindex) {
this.element.style.zIndex = this.originalZ;
}
if (this.options.endeffect) {
this.options.endeffect(this.element);
}
dr.Draggables.deactivate();
dr.Droppables.reset(this.element);
},
keyPress: function (event) {
if (event.keyString != "KEY_ESCAPE") {
return;
}
this.finishDrag(event, false);
event.stop();
},
endDrag: function (event) {
if (!this.dragging) {
return;
}
this.stopScrolling();
this.finishDrag(event, true);
event.stop();
},
draw: function (point) {
var pos = MochiKit.Position.cumulativeOffset(this.element);
var d = this.currentDelta();
pos.x -= d[0];
pos.y -= d[1];
if (this.options.scroll && !this.options.scroll.scrollTo) {
pos.x -= this.options.scroll.scrollLeft - this.originalScrollLeft;
pos.y -= this.options.scroll.scrollTop - this.originalScrollTop;
}
var p = [point.page.x - pos.x - this.offset[0],
point.page.y - pos.y - this.offset[1]]
if (this.options.snap) {
if (typeof(this.options.snap) == 'function') {
p = this.options.snap(p[0], p[1]);
} else {
if (this.options.snap instanceof Array) {
var i = -1;
p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
i += 1;
return Math.round(v/this.options.snap[i]) *
this.options.snap[i]
}, this), p)
} else {
p = MochiKit.Base.map(MochiKit.Base.bind(function (v) {
return Math.round(v/this.options.snap) *
this.options.snap
}, this), p)
}
}
}
var style = this.element.style;
if ((!this.options.constraint) ||
(this.options.constraint == 'horizontal')) {
style.left = p[0] + 'px';
}
if ((!this.options.constraint) ||
(this.options.constraint == 'vertical')) {
style.top = p[1] + 'px';
}
if (style.visibility == 'hidden') {
style.visibility = ''; // fix gecko rendering
}
},
stopScrolling: function () {
if (this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
}
},
startScrolling: function (speed) {
if (!speed[0] || !speed[1]) {
return;
}
this.scrollSpeed = [speed[0] * this.options.scrollSpeed,
speed[1] * this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(MochiKit.Base.bind(this.scroll, this), 10);
},
scroll: function () {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if (this.options.scroll == window) {
var s = this._getWindowScroll(this.options.scroll);
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo(s.left + d * this.scrollSpeed[0],
s.top + d * this.scrollSpeed[1]);
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
var d = MochiKit.DragAndDrop;
MochiKit.Position.prepare();
d.Droppables.show(d.Draggables._lastPointer, this.element);
this.draw(d.Draggables._lastPointer);
this.options.onchange(this);
},
_getWindowScroll: function (w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return {top: T, left: L, width: W, height: H};
},
repr: function () {
return '[' + this.__class__.NAME + ", options:" + MochiKit.Base.repr(this.options) + "]";
}
};
MochiKit.DragAndDrop.__new__ = function () {
MochiKit.Base.nameFunctions(this);
this.EXPORT_TAGS = {
":common": this.EXPORT,
":all": MochiKit.Base.concat(this.EXPORT, this.EXPORT_OK)
};
};
MochiKit.DragAndDrop.__new__();
MochiKit.Base._exportSymbols(this, MochiKit.DragAndDrop);

5773
fas/fas/static/javascript/MochiKit.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,366 @@
MochiKit.Base.update(MochiKit.Base, {
isIE: function () {
return /MSIE/.test(navigator.userAgent);
},
isGecko: function () {
return /Gecko/.test(navigator.userAgent);
},
isKHTML: function () {
return /Konqueror|Safari|KHTML/.test(navigator.userAgent)
},
isSafari: function () {
return navigator.appVersion.indexOf('AppleWebKit') > 0;
},
isOpera: function () {
return navigator.userAgent.indexOf('Opera') > 0;
}
});
MochiKit.Base.update(MochiKit.DOM, {
getStyle: function (element, style) {
element = MochiKit.DOM.getElement(element);
var value = element.style[MochiKit.Base.camelize(style)];
if (!value) {
if (document.defaultView && document.defaultView.getComputedStyle) {
var css = document.defaultView.getComputedStyle(element, null);
value = css ? css.getPropertyValue(style) : null;
} else if (element.currentStyle) {
value = element.currentStyle[MochiKit.Base.camelize(style)];
}
}
if (MochiKit.Base.isOpera() && (MochiKit.Base.find(['left', 'top', 'right', 'bottom'], style))) {
if (MochiKit.DOM.getStyle(element, 'position') == 'static') {
value = 'auto';
}
}
return value == 'auto' ? null : value;
},
setStyle: function (element, style) {
element = MochiKit.DOM.getElement(element);
for (name in style) {
element.style[MochiKit.Base.camelize(name)] = style[name];
}
},
getOpacity: function (element) {
var opacity;
if (opacity = MochiKit.DOM.getStyle(element, 'opacity')) {
return parseFloat(opacity);
}
if (opacity = (MochiKit.DOM.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) {
if (opacity[1]) {
return parseFloat(opacity[1]) / 100;
}
}
return 1.0;
},
getInlineOpacity: function (element) {
return MochiKit.DOM.getElement(element).style.opacity || '';
},
setOpacity: function (element, value) {
element = MochiKit.DOM.getElement(element);
if (value == 1) {
MochiKit.DOM.setStyle(element, {opacity:
(MochiKit.Base.isGecko() && !MochiKit.Base.isKHTML()) ?
0.999999 : null});
if (MochiKit.Base.isIE())
MochiKit.DOM.setStyle(element, {filter:
MochiKit.DOM.getStyle(element, 'filter').replace(/alpha\([^\)]*\)/gi, '')});
} else {
if (value < 0.00001) {
value = 0;
}
MochiKit.DOM.setStyle(element, {opacity: value});
if (MochiKit.Base.isIE()) {
MochiKit.DOM.setStyle(element,
{filter: MochiKit.DOM.getStyle(element, 'filter').replace(/alpha\([^\)]*\)/gi, '') + 'alpha(opacity=' + value * 100 + ')' });
}
}
},
isVisible: function (element) {
return MochiKit.DOM.getElement(element).style.display != 'none';
},
makeClipping: function (element) {
element = MochiKit.DOM.getElement(element);
if (element._overflow) {
return;
}
element._overflow = element.style.overflow;
if ((MochiKit.DOM.getStyle(element, 'overflow') || 'visible') != 'hidden') {
element.style.overflow = 'hidden';
}
},
undoClipping: function (element) {
element = MochiKit.DOM.getElement(element);
if (!element._overflow) {
return;
}
element.style.overflow = element._overflow;
element._overflow = undefined;
},
makePositioned: function (element) {
element = MochiKit.DOM.getElement(element);
/*if (!element.style) {
alert(element);
}*/
var pos = MochiKit.DOM.getStyle(element, 'position');
if ((pos == 'static' || !pos) && !element._madePositioned) {
element._madePositioned = true;
element.style.position = 'relative';
// Opera returns the offset relative to the positioning context,
// when an element is position relative but top and left have
// not been defined
if (MochiKit.Base.isOpera()) {
element.style.top = 0;
element.style.left = 0;
}
}
},
undoPositioned: function (element) {
element = MochiKit.DOM.getElement(element);
if (element._madePositioned) {
element._madePositioned = undefined;
element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = '';
}
},
getFirstElementByTagAndClassName: function (tagName, className,
/* optional */parent) {
var self = MochiKit.DOM;
if (typeof(tagName) == 'undefined' || tagName === null) {
tagName = '*';
}
if (typeof(parent) == 'undefined' || parent === null) {
parent = self._document;
}
parent = self.getElement(parent);
var children = (parent.getElementsByTagName(tagName)
|| self._document.all);
if (typeof(className) == 'undefined' || className === null) {
return MochiKit.Base.extend(null, children);
}
for (var i = 0; i < children.length; i++) {
var child = children[i];
var classNames = child.className.split(' ');
for (var j = 0; j < classNames.length; j++) {
if (classNames[j] == className) {
return child;
}
}
}
},
isParent: function (child, element) {
if (!child.parentNode || child == element) {
return false;
}
if (child.parentNode == element) {
return true;
}
return MochiKit.DOM.isParent(child.parentNode, element);
}
});
MochiKit.Position = {
// set to true if needed, warning: firefox performance problems
// NOT neeeded for page scrolling, only if draggable contained in
// scrollable elements
includeScrollOffsets: false,
prepare: function () {
var deltaX = window.pageXOffset
|| document.documentElement.scrollLeft
|| document.body.scrollLeft
|| 0;
var deltaY = window.pageYOffset
|| document.documentElement.scrollTop
|| document.body.scrollTop
|| 0;
this.windowOffset = new MochiKit.Style.Coordinates(deltaX, deltaY);
},
cumulativeOffset: function (element) {
var valueT = 0;
var valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
realOffset: function (element) {
var valueT = 0;
var valueL = 0;
do {
valueT += element.scrollTop || 0;
valueL += element.scrollLeft || 0;
element = element.parentNode;
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
within: function (element, x, y) {
if (this.includeScrollOffsets) {
return this.withinIncludingScrolloffsets(element, x, y);
}
this.xcomp = x;
this.ycomp = y;
this.offset = this.cumulativeOffset(element);
if (element.style.position == "fixed") {
this.offset.x += this.windowOffset.x;
this.offset.y += this.windowOffset.y;
}
return (y >= this.offset.y &&
y < this.offset.y + element.offsetHeight &&
x >= this.offset.x &&
x < this.offset.x + element.offsetWidth);
},
withinIncludingScrolloffsets: function (element, x, y) {
var offsetcache = this.realOffset(element);
this.xcomp = x + offsetcache.x - this.windowOffset.x;
this.ycomp = y + offsetcache.y - this.windowOffset.y;
this.offset = this.cumulativeOffset(element);
return (this.ycomp >= this.offset.y &&
this.ycomp < this.offset.y + element.offsetHeight &&
this.xcomp >= this.offset.x &&
this.xcomp < this.offset.x + element.offsetWidth);
},
// within must be called directly before
overlap: function (mode, element) {
if (!mode) {
return 0;
}
if (mode == 'vertical') {
return ((this.offset.y + element.offsetHeight) - this.ycomp) /
element.offsetHeight;
}
if (mode == 'horizontal') {
return ((this.offset.x + element.offsetWidth) - this.xcomp) /
element.offsetWidth;
}
},
absolutize: function (element) {
element = MochiKit.DOM.getElement(element);
if (element.style.position == 'absolute') {
return;
}
MochiKit.Position.prepare();
var offsets = MochiKit.Position.positionedOffset(element);
var width = element.clientWidth;
var height = element.clientHeight;
var oldStyle = {
'position': element.style.position,
'left': offsets.x - parseFloat(element.style.left || 0),
'top': offsets.y - parseFloat(element.style.top || 0),
'width': element.style.width,
'height': element.style.height
};
element.style.position = 'absolute';
element.style.top = offsets.y + 'px';
element.style.left = offsets.x + 'px';
element.style.width = width + 'px';
element.style.height = height + 'px';
return oldStyle;
},
positionedOffset: function (element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
if (element) {
p = MochiKit.DOM.getStyle(element, 'position');
if (p == 'relative' || p == 'absolute') {
break;
}
}
} while (element);
return new MochiKit.Style.Coordinates(valueL, valueT);
},
relativize: function (element, oldPos) {
element = MochiKit.DOM.getElement(element);
if (element.style.position == 'relative') {
return;
}
MochiKit.Position.prepare();
var top = parseFloat(element.style.top || 0) -
(oldPos['top'] || 0);
var left = parseFloat(element.style.left || 0) -
(oldPos['left'] || 0);
element.style.position = oldPos['position'];
element.style.top = top + 'px';
element.style.left = left + 'px';
element.style.width = oldPos['width'];
element.style.height = oldPos['height'];
},
clone: function (source, target) {
source = MochiKit.DOM.getElement(source);
target = MochiKit.DOM.getElement(target);
target.style.position = 'absolute';
var offsets = this.cumulativeOffset(source);
target.style.top = offsets.y + 'px';
target.style.left = offsets.x + 'px';
target.style.width = source.offsetWidth + 'px';
target.style.height = source.offsetHeight + 'px';
},
page: function (forElement) {
var valueT = 0;
var valueL = 0;
var element = forElement;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
// Safari fix
if (element.offsetParent == document.body && MochiKit.DOM.getStyle(element, 'position') == 'absolute') {
break;
}
} while (element = element.offsetParent);
element = forElement;
do {
valueT -= element.scrollTop || 0;
valueL -= element.scrollLeft || 0;
} while (element = element.parentNode);
return new MochiKit.Style.Coordinates(valueL, valueT);
}
};

View file

@ -0,0 +1,531 @@
/***
Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
Mochi-ized By Thomas Herve (_firstname_@nimail.org)
See scriptaculous.js for full license.
***/
if (typeof(dojo) != 'undefined') {
dojo.provide('MochiKit.DragAndDrop');
dojo.require('MochiKit.Base');
dojo.require('MochiKit.DOM');
dojo.require('MochiKit.Iter');
}
if (typeof(JSAN) != 'undefined') {
JSAN.use("MochiKit.Base", []);
JSAN.use("MochiKit.DOM", []);
JSAN.use("MochiKit.Iter", []);
}
try {
if (typeof(MochiKit.Base) == 'undefined' ||
typeof(MochiKit.DOM) == 'undefined' ||
typeof(MochiKit.Iter) == 'undefined') {
throw "";
}
} catch (e) {
throw "MochiKit.DragAndDrop depends on MochiKit.Base, MochiKit.DOM and MochiKit.Iter!";
}
if (typeof(MochiKit.Sortable) == 'undefined') {
MochiKit.Sortable = {};
}
MochiKit.Sortable.NAME = 'MochiKit.Sortable';
MochiKit.Sortable.VERSION = '1.4';
MochiKit.Sortable.__repr__ = function () {
return '[' + this.NAME + ' ' + this.VERSION + ']';
};
MochiKit.Sortable.toString = function () {
return this.__repr__();
};
MochiKit.Sortable.EXPORT = [
"SortableObserver"
];
MochiKit.DragAndDrop.EXPORT_OK = [
"Sortable"
];
MochiKit.Sortable.SortableObserver = function (element, observer) {
this.__init__(element, observer);
};
MochiKit.Sortable.SortableObserver.prototype = {
/***
Observe events of drag and drop sortables.
***/
__init__: function (element, observer) {
this.element = MochiKit.DOM.getElement(element);
this.observer = observer;
this.lastValue = MochiKit.Sortable.Sortable.serialize(this.element);
},
onStart: function () {
this.lastValue = MochiKit.Sortable.Sortable.serialize(this.element);
},
onEnd: function () {
MochiKit.Sortable.Sortable.unmark();
if (this.lastValue != MochiKit.Sortable.Sortable.serialize(this.element)) {
this.observer(this.element)
}
}
};
MochiKit.Sortable.Sortable = {
/***
Manage sortables. Mainly use the create function to add a sortable.
***/
sortables: {},
_findRootElement: function (element) {
while (element.tagName != "BODY") {
if (element.id && MochiKit.Sortable.Sortable.sortables[element.id]) {
return element;
}
element = element.parentNode;
}
},
options: function (element) {
element = MochiKit.Sortable.Sortable._findRootElement(MochiKit.DOM.getElement(element));
if (!element) {
return;
}
return MochiKit.Sortable.Sortable.sortables[element.id];
},
destroy: function (element){
var s = MochiKit.Sortable.Sortable.options(element);
var b = MochiKit.Base;
var d = MochiKit.DragAndDrop;
if (s) {
d.Draggables.removeObserver(s.element);
b.map(function (dr) {
d.Droppables.remove(dr);
}, s.droppables);
b.map(function (dr) {
dr.destroy();
}, s.draggables);
delete MochiKit.Sortable.Sortable.sortables[s.element.id];
}
},
create: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable.Sortable;
options = MochiKit.Base.update({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
// also takes array of elements (or ids); or false
containment: [element],
handle: false, // or a CSS class
only: false,
hoverclass: null,
ghosting: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: /^[^_]*_(.*)$/,
onChange: MochiKit.Base.noop,
onUpdate: MochiKit.Base.noop,
accept: null
}, options);
// clear any old sortable with same element
self.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
ghosting: options.ghosting,
scroll: options.scroll,
scrollSensitivity: options.scrollSensitivity,
scrollSpeed: options.scrollSpeed,
constraint: options.constraint,
handle: options.handle
};
if (options.starteffect) {
options_for_draggable.starteffect = options.starteffect;
}
if (options.reverteffect) {
options_for_draggable.reverteffect = options.reverteffect;
} else if (options.ghosting) {
options_for_draggable.reverteffect = function (innerelement) {
innerelement.style.top = 0;
innerelement.style.left = 0;
};
}
if (options.endeffect) {
options_for_draggable.endeffect = options.endeffect;
}
if (options.zindex) {
options_for_draggable.zindex = options.zindex;
}
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass,
onhover: self.onHover,
tree: options.tree,
accept: options.accept
}
var options_for_tree = {
onhover: self.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass,
accept: options.accept
}
// fix for gecko engine
MochiKit.DOM.removeEmptyTextNodes(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if (options.dropOnEmpty || options.tree) {
new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
options.droppables.push(element);
}
MochiKit.Base.map(function (e) {
// handles are per-draggable
var handle = options.handle ?
MochiKit.DOM.getFirstElementByTagAndClassName(null,
options.handle, e) : e;
options.draggables.push(
new MochiKit.DragAndDrop.Draggable(e,
MochiKit.Base.update(options_for_draggable,
{handle: handle})));
new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
if (options.tree) {
e.treeNode = element;
}
options.droppables.push(e);
}, (self.findElements(element, options) || []));
if (options.tree) {
MochiKit.Base.map(function (e) {
new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
}, (self.findTreeElements(element, options) || []));
}
// keep reference
self.sortables[element.id] = options;
// for onupdate
MochiKit.DragAndDrop.Draggables.addObserver(
new MochiKit.Sortable.SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function (element, options) {
return MochiKit.Sortable.Sortable.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function (element, options) {
return MochiKit.Sortable.Sortable.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
findChildren: function (element, only, recursive, tagName) {
if (!element.hasChildNodes()) {
return null;
}
tagName = tagName.toUpperCase();
if (only) {
only = MochiKit.Base.flattenArray([only]);
}
var elements = [];
MochiKit.Base.map(function (e) {
if (e.tagName &&
e.tagName.toUpperCase() == tagName &&
(!only ||
MochiKit.Iter.some(only, function (c) {
return MochiKit.DOM.hasElementClass(e, c);
}))) {
elements.push(e);
}
if (recursive) {
var grandchildren = MochiKit.Sortable.Sortable.findChildren(e, only, recursive, tagName);
if (grandchildren && grandchildren.length > 0) {
elements = elements.concat(grandchildren);
}
}
}, element.childNodes);
return elements;
},
onHover: function (element, dropon, overlap) {
if (MochiKit.DOM.isParent(dropon, element)) {
return;
}
var self = MochiKit.Sortable.Sortable;
if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
return;
} else if (overlap > 0.5) {
self.mark(dropon, 'before');
if (dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = 'hidden'; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if (dropon.parentNode != oldParentNode) {
self.options(oldParentNode).onChange(element);
}
self.options(dropon.parentNode).onChange(element);
}
} else {
self.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if (nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = 'hidden'; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if (dropon.parentNode != oldParentNode) {
self.options(oldParentNode).onChange(element);
}
self.options(dropon.parentNode).onChange(element);
}
}
},
_offsetSize: function (element, type) {
if (type == 'vertical' || type == 'height') {
return element.offsetHeight;
} else {
return element.offsetWidth;
}
},
onEmptyHover: function (element, dropon, overlap) {
var oldParentNode = element.parentNode;
var self = MochiKit.Sortable.Sortable;
var droponOptions = self.options(dropon);
if (!MochiKit.DOM.isParent(dropon, element)) {
var index;
var children = self.findElements(dropon, {tag: droponOptions.tag,
only: droponOptions.only});
var child = null;
if (children) {
var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
offset -= self._offsetSize(children[index], droponOptions.overlap);
} else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
self.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function () {
var m = MochiKit.Sortable.Sortable._marker;
if (m) {
MochiKit.Style.hideElement(m);
}
},
mark: function (dropon, position) {
// mark on ghosting only
var d = MochiKit.DOM;
var self = MochiKit.Sortable.Sortable;
var sortable = self.options(dropon.parentNode);
if (sortable && !sortable.ghosting) {
return;
}
if (!self._marker) {
self._marker = d.getElement('dropmarker') ||
document.createElement('DIV');
MochiKit.Style.hideElement(self._marker);
d.addElementClass(self._marker, 'dropmarker');
self._marker.style.position = 'absolute';
document.getElementsByTagName('body').item(0).appendChild(self._marker);
}
var offsets = MochiKit.Position.cumulativeOffset(dropon);
self._marker.style.left = offsets.x + 'px';
self._marker.style.top = offsets.y + 'px';
if (position == 'after') {
if (sortable.overlap == 'horizontal') {
self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
} else {
self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
}
}
MochiKit.Style.showElement(self._marker);
},
_tree: function (element, options, parent) {
var self = MochiKit.Sortable.Sortable;
var children = self.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) {
continue;
}
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
}
/* Get the element containing the children and recurse over it */
if (child.container) {
self._tree(child.container, options, child)
}
parent.children.push (child);
}
return parent;
},
/* Finds the first element of the given tag type within a parent element.
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
_findChildrenElement: function (element, containerTag) {
if (element && element.hasChildNodes) {
for (var i = 0; i < element.childNodes.length; ++i) {
if (element.childNodes[i].tagName == containerTag) {
return element.childNodes[i];
}
}
}
return null;
},
tree: function (element, options) {
element = MochiKit.DOM.getElement(element);
var sortableOptions = MochiKit.Sortable.Sortable.options(element);
options = MochiKit.Base.update({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, options || {});
var root = {
id: null,
parent: null,
children: new Array,
container: element,
position: 0
}
return MochiKit.Sortable.Sortable._tree(element, options, root);
},
setSequence: function (element, newSequence, options) {
var self = MochiKit.Sortable.Sortable;
var b = MochiKit.Base;
element = MochiKit.DOM.getElement(element);
options = b.update(self.options(element), options || {});
var nodeMap = {};
b.map(function (n) {
var m = n.id.match(options.format);
if (m) {
nodeMap[m[1]] = [n, n.parentNode];
}
n.parentNode.removeChild(n);
}, self.findElements(element, options));
b.map(function (ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
}, newSequence);
},
/* Construct a [i] index for a particular node */
_constructIndex: function (node) {
var index = '';
do {
if (node.id) {
index = '[' + node.position + ']' + index;
}
} while ((node = node.parent) != null);
return index;
},
sequence: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable.Sortable;
var options = MochiKit.Base.update(self.options(element), options || {});
return MochiKit.Base.map(function (item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
}, MochiKit.DOM.getElement(self.findElements(element, options) || []));
},
serialize: function (element, options) {
element = MochiKit.DOM.getElement(element);
var self = MochiKit.Sortable.Sortable;
options = MochiKit.Base.update(self.options(element), options || {});
var name = encodeURIComponent(options.name || element.id);
if (options.tree) {
return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
return [name + self._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}, self.tree(element, options).children)).join('&');
} else {
return MochiKit.Base.map(function (item) {
return name + "[]=" + encodeURIComponent(item);
}, self.sequence(element, options)).join('&');
}
}
};

View file

@ -0,0 +1,57 @@
function getSelected() {
return getElement("foreback").value;
}
function updateDisplay() {
var textbox = getElement("as_string");
var current = getSelected();
if (current == "Foreground") {
textbox.value = Color.fromText("sample").toString();
} else {
textbox.value = Color.fromBackground("sample").toString();
}
}
function setSampleFromElement(elem, toSet) {
var elem = getElement(elem);
var samplediv = getElement("sample");
var color = Color.fromString(elem.value);
if (color == null) {
alert("Unknown color string");
return;
}
samplediv.style[toSet] = color;
updateDisplay();
}
function setColor() {
var current = getSelected();
if (current == "Foreground") {
setSampleFromElement("as_string", "color");
} else {
setSampleFromElement("as_string", "background");
}
}
function setForeground() {
setSampleFromElement("foreground", "color");
}
function setBackground() {
setSampleFromElement("background", "background");
}
function cloneColor() {
var samplediv = getElement("sample");
var current = getSelected();
if (current == "Foreground") {
samplediv.style.color = Color.fromText("header");
} else {
samplediv.style.background = Color.fromBackground("header");
}
updateDisplay();
}

View file

@ -0,0 +1,120 @@
/*
Drag: A Really Simple Drag Handler
*/
var nextInChain = [];
RollingSign = function(element, newstring, options) {
this.__init__(element, newstring, options);
}
RollingSign.prototype = new Base();
update(RollingSign.prototype, {
__init__ : function(element, newstring, options) {
this.element = getElement(element);
options = update(
{
charSpaceSize: 26,
startingChar: 65
}, options || {});
this.newstring = newstring;
this.oldstring = this.element.innerHTML;
this.start(options);
},
update: function(position) {
curchar = this.newstring.length * position;
toMake = this.newstring.length - curchar;
ending = "";
for (i = 0; i < toMake; i++) {
ending += String.fromCharCode(
Math.random() * this.options.charSpaceSize +
this.options.startingChar);
}
this.element.innerHTML = this.newstring.substr(0, curchar) +
ending;
},
finish: function() {
this.element.innerHTML = this.newstring;
}
}
);
Drag = {
_move: null,
_down: null,
start: function(e) {
e.stop();
// We need to remember what we're dragging.
Drag._target = e.target();
/*
There's no cross-browser way to get offsetX and offsetY, so we
have to do it ourselves. For performance, we do this once and
cache it.
*/
Drag._offset = Drag._diff(
e.mouse().page,
getElementPosition(Drag._target));
Drag._move = connect(document, 'onmousemove', Drag._drag);
Drag._down = connect(document, 'onmouseup', Drag._stop);
},
_offset: null,
_target: null,
_diff: function(lhs, rhs) {
return new MochiKit.Style.Coordinates(lhs.x - rhs.x, lhs.y - rhs.y);
},
_drag: function(e) {
e.stop();
setElementPosition(
Drag._target,
Drag._diff(e.mouse().page, Drag._offset));
},
_stop: function(e) {
disconnect(Drag._move);
disconnect(Drag._down);
}
};
connect(window, 'onload',
function() {
/*
Find all DIVs tagged with the draggable class, and connect them to
the Drag handler.
*/
var d = getElementsByTagAndClassName('DIV', 'draggable');
forEach(d,
function(elem) {
connect(elem, 'onmousedown', Drag.start);
});
});
connect(window, 'onload',
function() {
var elems = getElementsByTagAndClassName("A", "view-source");
var page = "draggable/";
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
var href = elem.href.split(/\//).pop();
elem.target = "_blank";
elem.href = "../view-source/view-source.html#" + page + href;
}
});

View file

@ -0,0 +1,35 @@
function cancelEdit(element) {
switchOff(element + 'Form')
appear(element)
}
function formEdit(element) {
var elements = Array("givenName", "mail", "fedoraPersonBugzillaMail", "fedoraPersonIrcNick", "fedoraPersonKeyId", "telephoneNumber", "postalAddress", "description")
for(var i = 0; i != elements.length; i++) {
if (elements[i] != element) {
var form = document.getElementById(elements[i] + 'Form');
if ( form.style.display != 'none') {
new Highlight(elements[i] + 'Form');
return false;
}
}
}
switchOff(element);
appear(element + 'Form');
}
function displayHelp(helpID) {
grow('helpMessageMain');
getElement('helpMessage').innerHTML = 'Please Wait...';
//d = MochiKit.Async.doSimpleXMLHttpRequest('/fas/help', {});
var d = loadJSONDoc('/fas/help', {helpID: helpID});
var gotMetadata = function (meta) {
getElement('helpMessage').innerHTML = meta.help
};
//getElement('helpMessage').innerHTML = d.help;
var metadataFetchFailed = function (err) {
getElement('helpMessage').innerHTML = 'Could not fetch help message!'
};
d.addCallbacks(gotMetadata, metadataFetchFailed)
return false;
}

View file

@ -0,0 +1,13 @@
var currentSelected = null;
function changeSelected(e) {
if (currentSelected != null) {
logDebug("Disconnecting " + currentSelected);
disconnectAll(currentSelected);
}
var es = getElement("elemselect");
currentSelected = es.value;
var ev = getElement("eventselect").value;
logDebug("Connecting " + currentSelected + " for event " + ev);
connect(currentSelected, ev, log);
}

View file

@ -0,0 +1,203 @@
/*
On page load, the SortableManager:
- Finds the table by its id (sortable_table).
- Parses its thead for columns with a "mochi:format" attribute.
- Parses the data out of the tbody based upon information given in the
"mochi:format" attribute, and clones the tr elements for later re-use.
- Clones the column header th elements for use as a template when drawing
sort arrow columns.
- Stores away a reference to the tbody, as it will be replaced on each sort.
- Performs the first sort.
On sort request:
- Sorts the data based on the given key and direction
- Creates a new tbody from the rows in the new ordering
- Replaces the column header th elements with clickable versions, adding an
indicator (&uarr; or &darr;) to the most recently sorted column.
*/
SortableManager = function () {
this.thead = null;
this.tbody = null;
this.columns = [];
this.rows = [];
this.sortState = {};
this.sortkey = 0;
};
mouseOverFunc = function () {
addElementClass(this, "over");
};
mouseOutFunc = function () {
removeElementClass(this, "over");
};
ignoreEvent = function (ev) {
if (ev && ev.preventDefault) {
ev.preventDefault();
ev.stopPropagation();
} else if (typeof(event) != 'undefined') {
event.cancelBubble = false;
event.returnValue = false;
}
};
update(SortableManager.prototype, {
"initWithTable": function (table) {
/***
Initialize the SortableManager with a table object
***/
// Ensure that it's a DOM element
table = getElement(table);
// Find the thead
this.thead = table.getElementsByTagName('thead')[0];
// get the mochi:format key and contents for each column header
var cols = this.thead.getElementsByTagName('th');
for (var i = 0; i < cols.length; i++) {
var node = cols[i];
var attr = null;
try {
attr = node.getAttribute("mochi:format");
} catch (err) {
// pass
}
var o = node.childNodes;
this.columns.push({
"format": attr,
"element": node,
"proto": node.cloneNode(true)
});
}
// scrape the tbody for data
this.tbody = table.getElementsByTagName('tbody')[0];
// every row
var rows = this.tbody.getElementsByTagName('tr');
for (var i = 0; i < rows.length; i++) {
// every cell
var row = rows[i];
var cols = row.getElementsByTagName('td');
var rowData = [];
for (var j = 0; j < cols.length; j++) {
// scrape the text and build the appropriate object out of it
var cell = cols[j];
var obj = scrapeText(cell);
switch (this.columns[j].format) {
case 'isodate':
obj = isoDate(obj);
break;
case 'str':
break;
case 'istr':
obj = obj.toLowerCase();
break;
// cases for numbers, etc. could be here
default:
break;
}
rowData.push(obj);
}
// stow away a reference to the TR and save it
rowData.row = row.cloneNode(true);
this.rows.push(rowData);
}
// do initial sort on first column
this.drawSortedRows(this.sortkey, true, false);
},
"onSortClick": function (name) {
/***
Return a sort function for click events
***/
return method(this, function () {
log('onSortClick', name);
var order = this.sortState[name];
if (order == null) {
order = true;
} else if (name == this.sortkey) {
order = !order;
}
this.drawSortedRows(name, order, true);
});
},
"drawSortedRows": function (key, forward, clicked) {
/***
Draw the new sorted table body, and modify the column headers
if appropriate
***/
log('drawSortedRows', key, forward);
this.sortkey = key;
// sort based on the state given (forward or reverse)
var cmp = (forward ? keyComparator : reverseKeyComparator);
this.rows.sort(cmp(key));
// save it so we can flip next time
this.sortState[key] = forward;
// get every "row" element from this.rows and make a new tbody
var newBody = TBODY(null, map(itemgetter("row"), this.rows));
// swap in the new tbody
this.tbody = swapDOM(this.tbody, newBody);
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
var node = col.proto.cloneNode(true);
// remove the existing events to minimize IE leaks
col.element.onclick = null;
col.element.onmousedown = null;
col.element.onmouseover = null;
col.element.onmouseout = null;
// set new events for the new node
node.onclick = this.onSortClick(i);
node.onmousedown = ignoreEvent;
node.onmouseover = mouseOverFunc;
node.onmouseout = mouseOutFunc;
// if this is the sorted column
if (key == i) {
// \u2193 is down arrow, \u2191 is up arrow
// forward sorts mean the rows get bigger going down
var arrow = (forward ? "\u2193" : "\u2191");
// add the character to the column header
node.appendChild(SPAN(null, arrow));
if (clicked) {
node.onmouseover();
}
}
// swap in the new th
col.element = swapDOM(col.element, node);
}
}
});
sortableManager = new SortableManager();
addLoadEvent(function () {
sortableManager.initWithTable('sortable_table');
});
// rewrite the view-source links
addLoadEvent(function () {
var elems = getElementsByTagAndClassName("A", "view-source");
var page = "sortable_tables/";
for (var i = 0; i < elems.length; i++) {
var elem = elems[i];
var href = elem.href.split(/\//).pop();
elem.target = "_blank";
elem.href = "../view-source/view-source.html#" + page + href;
}
});

View file

@ -0,0 +1,67 @@
// getElementDimensions, getElementPosition
function updateStatus() {
var e = getElement("thetestelem");
var dim = getElementDimensions(e);
var pos = getElementPosition(e);
getElement("dimensions").innerHTML = repr(dim);
getElement("coordinates").innerHTML = repr(pos);
getElement("width").value = dim.w;
getElement("height").value = dim.h;
getElement("x").value = pos.x;
getElement("y").value = pos.y;
}
// showElement and hideElement
function hideTheTestElem() {
// Toggles our guinea testelem element
var button = getElement("hidebutton");
if (button.value == "Hide Element") {
hideElement("thetestelem");
button.value = "Show Element";
} else {
showElement("thetestelem");
button.value = "Hide Element";
}
updateStatus();
}
// setElementDimensions
function setTestElemDimensions() {
var e = getElement("thetestelem");
var dim = new Dimensions(getElement("width").value,
getElement("height").value);
setElementDimensions(e, dim);
updateStatus();
}
// setElementPosition
function setTestElemPosition() {
var e = getElement("thetestelem");
var pos = new Coordinates(getElement("x").value,
getElement("y").value);
setElementPosition(e, pos);
updateStatus();
}
// setOpacity
function setTestElemOpacity() {
setOpacity("thetestelem", getElement("opacity").value);
}
// computedStyle
function getTestElemStyle() {
var prop = getElement("testelemprop").value;
var style = computedStyle("thetestelem", prop);
getElement("testelemstyle").innerHTML = style;
}

21
fas/fas/t.py Normal file
View file

@ -0,0 +1,21 @@
class test:
__var = 'Uninitalized'
def __getattr__(self, attr):
return self.__getattr__(attr)
def __setattr__(self, attr, value):
self.__dict__[attr] = value
@classmethod
def start(cls):
self = cls()
return self
p = test.start()
p.var='first'
t = test.start()
t.var='second'
print p.var
print t.var

View file

Binary file not shown.

View file

@ -0,0 +1,14 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<style type="text/css">
@import "/static/css/fas.css";
</style>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,19 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#">
<head>
<style type="text/css">
@import "/static/css/fas.css";
</style>
</head>
<body>
<div py:if="tg_flash" class="flash" py:content="tg_flash"></div>
<form method='post'>
<!-- This needs to be fixed before going live -->
<input type='hidden' name='attribute' value='${attribute}'/>
<input type='hidden' name='update' value='True'/>
<input type='hidden' name='userName' value='${userName}' />
<input type='text' name='value' value='${value}'/>
</form>
</body>
</html>

View file

@ -0,0 +1,236 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<style type="text/css">
@import "/static/css/fas.css";
@import "/static/css/draggable.css";
</style>
</head>
<body>
<div class='draggable white' style="left: 10px; display: none" id='help'>close</div>
<script src="/fas/static/javascript/forms.js" type="text/javascript"></script>
<script src="/fas/static/javascript/draggable.js" type="text/javascript"></script>
<h2 id="your-account-header"><img src="static/images/header-icon_account.png" />Your Fedora Account</h2>
<table class="account-info" id="your-account-basic-info">
<tbody>
<tr>
<td>Account Name <a href='' onClick="displayHelp('cn'); return false;">(?)</a>:</td>
<td>${user.cn}</td>
</tr>
<tr>
<td>Real Name <a href='' onClick="displayHelp('givenName'); return false;">(?)</a>:</td>
<td>
<div id='givenName'>${user.givenName} <a href="" onclick="formEdit('givenName'); return false;">(edit)</a></div>
<div id='givenNameForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='givenName'/>
<input type='text' name='value' value='${user.givenName}'/>
<a href='' onclick="cancelEdit('givenName'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Email <a href='' onClick="displayHelp('mail'); return false;">(?)</a>:</td>
<td>
<div id='mail'>${user.mail} <a href='' onclick="formEdit('mail'); return false;">(edit)</a></div>
<div id='mailForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='mail'/>
<input type='text' name='value' value='${user.mail}'/>
<a href='editAccount' onclick="cancelEdit('mail'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Bugzilla Email <a href='' onClick="displayHelp('fedoraPersonBugzillaMail'); return false;">(?)</a>:</td>
<td>
<div id='fedoraPersonBugzillaMail'>${user.fedoraPersonBugzillaMail} <a href='' onclick="formEdit('fedoraPersonBugzillaMail'); return false;">(edit)</a></div>
<div id='fedoraPersonBugzillaMailForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='fedoraPersonBugzillaMail'/>
<input type='text' name='value' value='${user.fedoraPersonBugzillaMail}'/>
<a href='editAccount' onclick="cancelEdit('fedoraPersonBugzillaMail'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>IRC Nick <a href='' onClick="displayHelp('fedoraPersonIrcNick'); return false;">(?)</a>:</td>
<td>
<div id='fedoraPersonIrcNick'>${user.fedoraPersonIrcNick} <a href='' onclick="formEdit('fedoraPersonIrcNick'); return false;">(edit)</a></div>
<div id='fedoraPersonIrcNickForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='fedoraPersonIrcNick'/>
<input type='text' name='value' value='${user.fedoraPersonIrcNick}'/>
<a href='editAccount' onclick="cancelEdit('fedoraPersonIrcNick'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>PGP Key <a href='' onClick="displayHelp('fedoraPersonKeyId'); return false;">(?)</a>:</td>
<td>
<div id='fedoraPersonKeyId'>${user.fedoraPersonKeyId} <a href='' onclick="formEdit('fedoraPersonKeyId'); return false;">(edit)</a></div>
<div id='fedoraPersonKeyIdForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='fedoraPersonKeyId'/>
<input type='text' name='value' value='${user.fedoraPersonKeyId}'/>
<a href='editAccount' onclick="cancelEdit('fedoraPersonKeyId'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Telephone Number <a href='' onClick="displayHelp('telephoneNumber'); return false;">(?)</a>:</td>
<td>
<div id='telephoneNumber'>${user.telephoneNumber} <a href='' onclick="formEdit('telephoneNumber'); return false;">(edit)</a></div>
<div id='telephoneNumberForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='telephoneNumber'/>
<input type='text' name='value' value='${user.telephoneNumber}'/>
<a href='editAccount' onclick="cancelEdit('telephoneNumber'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Postal Address <a href='' onClick="displayHelp('postalAddress'); return false;">(?)</a>:</td>
<td>
<div id='postalAddress'><pre>${user.postalAddress}</pre><a href='' onclick="formEdit('postalAddress'); return false;">(edit)</a></div>
<div id='postalAddressForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='postalAddress'/>
<textarea name='value'>${user.postalAddress}</textarea>
<input type='submit' value='submit'/>
<a href='editAccount' onclick="cancelEdit('postalAddress'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Description <a href='' onClick="displayHelp('description'); return false;">(?)</a>:</td>
<td>
<div id='description'><pre>${user.description}</pre><a href='' onclick="formEdit('description'); return false;">(edit)</a></div>
<div id='descriptionForm' style='display: none'>
<form method='post' action='editUserAttribute'>
<input type='hidden' name='userName' value='${user.cn}'/>
<input type='hidden' name='attribute' value='description'/>
<textarea name='value'>${user.description}</textarea>
<input type='submit' value='submit'/>
<a href='editAccount' onclick="cancelEdit('description'); return false;">(cancel)</a>
</form>
</div>
</td>
</tr>
<tr>
<td>Password <a href='' onClick="displayHelp('password'); return false;">(?)</a>: </td>
<td><img src="static/images/status_approved.png" />
Valid <span class="edit-button"><a href="resetPassword">(change)</a></span>
</td>
</tr>
<tr>
<td>Account Status <a href='' onClick="displayHelp('accountStatus'); return false;">(?)</a>:</td>
<td><img src="static/images/status_approved.png" />
Approved, Active <span class="edit-button"><a href="#">(deactivate)</a></span>
</td>
</tr>
<tr>
<td>CLA <a href='' onClick="displayHelp('cla'); return false;">(?)</a>:</td>
<td py:if='claDone'><img src='static/images/status_approved.png'/> Done <a href="#">(?)</a></td>
<td py:if='not claDone'><img src='static/images/status_incomplete.png'/> Not Done <a href="#">(?)</a></td>
</tr>
<!-- <tr>
<td>Description:</td>
<td>
<div id="description_form" style="display: none"><form method='post' action='editUserAttribute'>
<input type='hidden' name='attribute' value='description'/>
<textarea name='value'>${user.description}</textarea>
<input type='submit' value='submit'/>
<a href='editAccount'>(Cancel)</a>
</form></div>
<div id='description_actual'>
<pre>${user.description}</pre>
</div>
<a href='#' onClick="toggle('description_form'); return false;">Edit</a>
</td>
<td py:if='not action == "description"'><pre>${user.description}</pre>
<span class="edit-button">
<a href="${tg.url('', action='description')}">(edit)</a>
</span>-
</td>
</tr>-->
</tbody>
</table>
<h2 id="your-account-header-roles">Your Roles</h2>
<h2 py:if="groupsPending">Pending</h2>
<div id='gpShow' style='display: none'><a href='' onclick="blindDown('groupsPending'); fade('gpShow'); appear('gpHide'); return false;">Show</a></div>
<div id='gpHide'><a href='' onclick="blindUp('groupsPending'); fade('gpHide'); appear('gpShow'); return false;">Hide</a></div>
<div id='groupsPending'>
<ul class="tool-links">
<li py:for='group in groupsPending'><img src="static/images/status_incomplete.png"/> ${groupsPending[group].cn} <a href="${tg.url('editGroup', groupName=groupsPending[group].cn)}">(edit)</a></li>
</ul>
</div>
<h2 py:if="groups">Approved</h2>
<div id='gShow'><a href='' onclick="blindDown('groups'); fade('gShow'); appear('gHide'); return false;">Show</a></div>
<div id='gHide' style='display: none'><a href='' onclick="blindUp('groups'); fade('gHide'); appear('gShow'); return false;">Hide</a></div>
<div id='groups' style='display: none'>
<ul class="tool-links">
<li py:for='group in groups'><img src="static/images/status_approved.png"/> ${groups[group].cn} <a href="${tg.url('editGroup', groupName=groups[group].cn)}">(edit)</a></li>
</ul>
</div>
<h2>Misc</h2>
<table class="account-info your-account-role-info">
<tbody>
<tr>
<td>Status:</td>
<td><img src="static/images/status_approved.png" />
Approved, Active <a href="#">(edit)</a>
</td>
</tr><tr>
<td>Tools:</td>
<td>
<ul class="tool-links">
<li><a href="listGroup">Apply for new group ...</a></li>
<li><a href="invite">Invite a New Member ...</a></li>
<li><a href="#">View All Pending Membership Requests ...</a></li>
</ul>
</td>
</tr><tr>
<td>Queue:</td>
<td>
<ul class="queue-links">
<li><a href="#">Chewbacca D. Wookiee requests approval to join project as <strong>user</strong> ...</a></li>
<li><a href="#">Gaius Baltar request approval to upgrade from <strong>user</strong> to <strong>administrator</strong> ...</a></li>
<li><a href="#">Leia Organa requests approval to upgrade from <strong>user</strong> to <strong>sponsor</strong> ...</a></li>
</ul>
</td>
</tr>
</tbody>
</table>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,88 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'"
xmlns:mochi="MyUniqueMochiUri">
<head>
<style type="text/css">
@import "/fas/static/css/fas.css";
@import '/fas/static/css/sortable_tables.css';
</style>
</head>
<body>
<script src="/fas/static/javascript/sortable_tables.js" type="text/javascript"></script>
<h1>My Status: ${me.fedoraRoleStatus}</h1>
<form py:if="'Not a Member' in me.fedoraRoleStatus" action='applyForGroup'>
<input type='hidden' name='groupName' value='${group.cn}'/>
<input type='text' name='requestField' value='Please let me join..'/>
<input type='submit' name='action' value='Join'/>
</form>
<a py:if="'Not a Member' not in me.fedoraRoleStatus" href="${tg.url('applyForGroup', groupName=group.cn, action='Remove')}">Remove me</a>
<h2>${group.cn}</h2>
<table>
<!-- <script language="JavaScript" type="text/JavaScript">
AutoCompleteManager${field_id} = new AutoCompleteManager('${field_id}',
'${text_field.field_id}', '${hidden_field.field_id}',
'${search_controller}', '${search_param}', '${result_name}',${str(only_suggest).lower()},
'${tg.url([tg.widgets, 'turbogears.widgets/spinner.gif'])}', ${complete_delay});
addLoadEvent(AutoCompleteManager${field_id}.initialize);
</script>
-->
<tr><td>Name</td><td>${group.cn}</td></tr>
<tr><td>Owner</td><td>${group.fedoraGroupOwner}</td></tr>
<tr><td>Type</td><td>${group.fedoraGroupType}</td></tr>
<tr><td>Needs Sponsor</td><td>${group.fedoraGroupNeedsSponsor}</td></tr>
<tr><td>Self Removal</td><td>${group.fedoraGroupUserCanRemove}</td></tr>
<tr><td>fedoraGroupJoinMsg</td><td>${group.fedoraGroupJoinMsg}</td></tr>
</table>
<h2 py:if='me.fedoraRoleStatus == "approved"'>Invite <a href='' onClick="displayHelp('inviteToGroup'); return false;">(?)</a></h2>
<span py:if='me.fedoraRoleStatus == "approved"'>${searchUserForm(action='modifyGroup', value=value, method='get')}</span>
<h2>Members</h2>
<span>
<!-- <form action='modifyGroup'>
<input type='hidden' name='groupName' value='${group.cn}'/>
<input type='text' name='userName'/>
<input type='submit' name='action' value='apply'/>
</form>-->
</span>
<table id='sortable_table' class='datagrid'>
<thead>
<tr><th mochi:format="str">Username</th><th>Sponsor</th><th mochi:format="str">Date Added</th><th>Date Approved</th><th mochi:format="str">Approval</th><th>Role Type</th><th py:if='me.fedoraRoleType == "administrator" or me.fedoraRoleType == "sponsor"'>Action</th><th></th></tr>
</thead>
<tr py:for="user in groups">
<td><a href='editAccount?userName=${user}'>${user}</a></td>
<td py:if='not(groups[user].fedoraRoleSponsor == "None")'><a href='editAccount?userName=${groups[user].fedoraRoleSponsor}'>${groups[user].fedoraRoleSponsor}</a></td>
<td py:if='groups[user].fedoraRoleSponsor == "None"'>${groups[user].fedoraRoleSponsor}</td>
<td>${groups[user].fedoraRoleCreationDate}</td>
<td>${groups[user].fedoraRoleApprovalDate}</td>
<td>${groups[user].fedoraRoleStatus}</td>
<td>${groups[user].fedoraRoleType}</td>
<!--<td>${groups[user].fedoraRoleDomain}</td>-->
<!-- This section includes all action items -->
<td py:if='me.fedoraRoleType == "administrator"'>
<a py:if="group.fedoraGroupNeedsSponsor.upper() == 'TRUE'" href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='sponsor')}">Sponsor</a>
<a py:if="not group.fedoraGroupNeedsSponsor.upper() == 'TRUE' and groups[user].fedoraRoleStatus.lower() != 'approved'" href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='sponsor')}">Approve</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='remove')}">Delete</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='upgrade')}">Upgrade</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='downgrade')}">Downgrade</a> Suspend
</td>
<td py:if='me.fedoraRoleType == "sponsor" and not groups[user].fedoraRoleType == "administrator"'>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='sponsor')}" py:if="group.fedoraGroupNeedsSponsor.upper() == 'TRUE'">Sponsor</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='sponsor')}" py:if="not group.fedoraGroupNeedsSponsor.upper() == 'TRUE'">Approve</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='remove')}">Delete</a>
<a py:if='groups[user].fedoraRoleType' href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='upgrade')}">Upgrade</a>
<a href="${tg.url('modifyGroup', groupName=groups[user].cn, userName=user, action='downgrade')}">Downgrade</a> Suspend
<div py:if="'not' in '%s' % tg_flash and user in '%s' % tg_flash"> -- Error!</div>
</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" py:extends="'master.kid'">
<head>
<meta content="text/plain; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
<title>Crap!</title>
</head>
<body>
${exception}
</body>
</html>

View file

@ -0,0 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'"
xmlns:mochi="MyUniqueMochiUri">
<head>
<style type="text/css">
@import "/fas/static/css/fas.css";
@import '/fas/static/css/sortable_tables.css';
</style>
</head>
<body>
<script src="/fas/static/javascript/sortable_tables.js" type="text/javascript"></script>
<h1>List (${search})</h1>
<form method='GET'>
Search <input type='text' value='${search}' name='search' size='15'/> (Ex: "cvs*")
</form>
<table>
<tr>
<td width='10' align='center' py:for="letter in 'abcdefghijklmnopqrstuvwxyz'.upper()">
<a href='?search=${letter}*'>${letter}</a>
</td>
<td><a href='?search=*'>All</a></td>
</tr>
</table>
<table py:if="groups" id='sortable_table' class='datagrid'>
<thead>
<tr><th mochi:format="str">Group</th><th mochi:format='istr'>Status</th></tr>
</thead>
<tr py:for="group in groups">
<td>${groups[group].cn} <a href="editGroup?groupName=${groups[group].cn}">(edit)</a>
<a href='' onClick="displayHelp('info'); return false;">(info)</a></td>
<td align='center'>
<a py:if="groups[group].cn in myGroups" href="${tg.url('editGroup', groupName=groups[group].cn)}"><img src="static/images/status_approved.png" border='0'/></a>
<a py:if="groups[group].cn not in myGroups" href="${tg.url('editGroup', groupName=groups[group].cn)}"><img src="static/images/status_incomplete.png" border='0'/></a>
</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

View file

@ -0,0 +1,32 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'"
xmlns:mochi="MyUniqueMochiUri">
<head>
<style type="text/css">
@import "/fas/static/css/fas.css";
@import '/fas/static/css/sortable_tables.css';
</style>
</head>
<body>
<script src="/fas/static/javascript/sortable_tables.js" type="text/javascript"></script>
<h2 py:if="'userID' in builds.userLink" id="your-account-header"><img src="static/images/header-icon_account.png" /> Recent Builds <a href='${builds.userLink}'>(Koji)</a></h2>
<table py:if="'userID' in builds.userLink" id='sortable_table' class='datagrid'>
<thead>
<tr><th mochi:format='str'>Build</th><th mochi:format='date'>Build Date</th></tr>
</thead>
<!--<tr><td>Koji</td><td><a href='${builds.userLink}'>Build Info</a></td></tr>-->
<tr py:for="build in builds.builds">
<td>
<font py:if="'complete' in builds.builds[build]['title']" color='green'>${builds.builds[build]['title']}</font>
<font py:if="'failed' in builds.builds[build]['title']" color='red'>${builds.builds[build]['title']}</font>
<a href="${build}">(build)</a>
</td>
<td>${builds.builds[build]['pubDate']}</td>
</tr>
</table>
</body>
</html>

BIN
fas/fas/templates/home.pyc Normal file

Binary file not shown.

View file

@ -0,0 +1,40 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
py:extends="'master.kid'">
<head>
<style type="text/css">
@import "/static/css/fas.css";
</style>
</head>
<body>
<h1>Invite a new community member!</h1>
<form method='POST'>
To email: <input type='text' value='' name='target'/><br/>
From: ${user.mail}<br/>
Subject: Invitation to join the Fedora Team!<br/>
Message: Please come up with a better more inviting message.<br/>
<pre>
${user.givenName} &le;<a href='mailto: ${user.mail}'>${user.mail}</a>&ge; has invited you to join the Fedora
Project! We are a community of users and developers who produce a
complete operating system from entirely free and open source software
(FOSS). ${user.givenName} thinks that you have knowledge and skills
that make you a great fit for the Fedora community, and that you might
be interested in contributing.
How could you team up with the Fedora community to use and develop your
skills? Check out http://fedoraproject.org/wiki/Join for some ideas.
Our community is more than just software developers -- we also have a
place for you whether you're an artist, a web site builder, a writer, or
a people person. You'll grow and learn as you work on a team with other
very smart and talented people.
Fedora and FOSS are changing the world -- come be a part of it!
</pre>
<input type='submit'/>
</form>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show more