Initial import. Old location http://cvs.fedoraproject.org/viewcvs/accounts2/?root=fedora
9
fas/README.txt
Normal 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
|
@ -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
BIN
fas/devdata.sqlite.bak
Normal file
15
fas/fas.egg-info/PKG-INFO
Normal 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
|
21
fas/fas.egg-info/SOURCES.txt
Normal 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
|
1
fas/fas.egg-info/dependency_links.txt
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
fas/fas.egg-info/not-zip-safe
Normal file
|
@ -0,0 +1 @@
|
|||
|
2
fas/fas.egg-info/paster_plugins.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
TurboGears
|
||||
PasteScript
|
1
fas/fas.egg-info/requires.txt
Normal file
|
@ -0,0 +1 @@
|
|||
TurboGears >= 1.0.1
|
2
fas/fas.egg-info/sqlobject.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
db_module=fas.model
|
||||
history_dir=$base/fas/sqlobject-history
|
1
fas/fas.egg-info/top_level.txt
Normal file
|
@ -0,0 +1 @@
|
|||
fas
|
0
fas/fas/__init__.py
Normal file
BIN
fas/fas/__init__.pyc
Normal file
0
fas/fas/config/__init__.py
Normal file
132
fas/fas/config/app.cfg
Normal 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
|
@ -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
|
@ -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
440
fas/fas/fasLDAP.py
Normal 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
17
fas/fas/feeds.py
Normal 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
33
fas/fas/json.py
Normal 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
|
@ -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
|
@ -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"
|
28
fas/fas/static/css/draggable.css
Normal 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
|
@ -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;
|
||||
}
|
||||
|
66
fas/fas/static/css/sortable_tables.css
Normal 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; }
|
BIN
fas/fas/static/css/static/images/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
fas/fas/static/css/static/images/header-icon_account.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
fas/fas/static/css/static/images/header_inner.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
fas/fas/static/css/static/images/hr.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
fas/fas/static/css/static/images/icon_tool-item.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
fas/fas/static/css/static/images/icon_warning.png
Normal file
After Width: | Height: | Size: 573 B |
BIN
fas/fas/static/css/static/images/info.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
fas/fas/static/css/static/images/ok.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
fas/fas/static/css/static/images/status_approved.png
Normal file
After Width: | Height: | Size: 427 B |
BIN
fas/fas/static/css/static/images/status_incomplete.png
Normal file
After Width: | Height: | Size: 215 B |
BIN
fas/fas/static/css/static/images/status_rejected.png
Normal file
After Width: | Height: | Size: 435 B |
BIN
fas/fas/static/css/static/images/tg_under_the_hood.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
fas/fas/static/css/static/images/under_the_hood_blue.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
381
fas/fas/static/css/style.css
Normal 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;
|
||||
}
|
||||
|
BIN
fas/fas/static/images/account.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
fas/fas/static/images/approved.png
Normal file
After Width: | Height: | Size: 422 B |
BIN
fas/fas/static/images/arrow.png
Normal file
After Width: | Height: | Size: 548 B |
BIN
fas/fas/static/images/attn.png
Normal file
After Width: | Height: | Size: 610 B |
BIN
fas/fas/static/images/control-separator.png
Normal file
After Width: | Height: | Size: 172 B |
BIN
fas/fas/static/images/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
fas/fas/static/images/footer-bottom.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
fas/fas/static/images/footer-top.png
Normal file
After Width: | Height: | Size: 143 B |
BIN
fas/fas/static/images/head.png
Normal file
After Width: | Height: | Size: 169 B |
BIN
fas/fas/static/images/header-icon_account.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
fas/fas/static/images/header_inner.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
fas/fas/static/images/help.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
fas/fas/static/images/hr.png
Normal file
After Width: | Height: | Size: 193 B |
BIN
fas/fas/static/images/icon_tool-item.png
Normal file
After Width: | Height: | Size: 272 B |
BIN
fas/fas/static/images/icon_warning.png
Normal file
After Width: | Height: | Size: 573 B |
BIN
fas/fas/static/images/info.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
fas/fas/static/images/infobar.png
Normal file
After Width: | Height: | Size: 194 B |
BIN
fas/fas/static/images/logo.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
fas/fas/static/images/ok.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
fas/fas/static/images/queue.png
Normal file
After Width: | Height: | Size: 175 B |
BIN
fas/fas/static/images/shadow.png
Normal file
After Width: | Height: | Size: 163 B |
BIN
fas/fas/static/images/sidebar.png
Normal file
After Width: | Height: | Size: 230 B |
BIN
fas/fas/static/images/status_approved.png
Normal file
After Width: | Height: | Size: 427 B |
BIN
fas/fas/static/images/status_incomplete.png
Normal file
After Width: | Height: | Size: 215 B |
BIN
fas/fas/static/images/status_rejected.png
Normal file
After Width: | Height: | Size: 435 B |
BIN
fas/fas/static/images/success.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
fas/fas/static/images/tg_under_the_hood.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
fas/fas/static/images/tools.png
Normal file
After Width: | Height: | Size: 314 B |
BIN
fas/fas/static/images/topnav-separator.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
fas/fas/static/images/topnav.png
Normal file
After Width: | Height: | Size: 197 B |
BIN
fas/fas/static/images/unapproved.png
Normal file
After Width: | Height: | Size: 409 B |
BIN
fas/fas/static/images/under_the_hood_blue.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
774
fas/fas/static/javascript/DragAndDrop.js
Normal 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
366
fas/fas/static/javascript/New.js
Normal 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);
|
||||
}
|
||||
};
|
||||
|
531
fas/fas/static/javascript/Sortable.js
Normal 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('&');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
57
fas/fas/static/javascript/color.js
Normal 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();
|
||||
|
||||
}
|
120
fas/fas/static/javascript/draggable.js
Normal 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;
|
||||
}
|
||||
});
|
35
fas/fas/static/javascript/forms.js
Normal 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;
|
||||
}
|
13
fas/fas/static/javascript/signal.js
Normal 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);
|
||||
}
|
203
fas/fas/static/javascript/sortable_tables.js
Normal 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 (↑ or ↓) 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;
|
||||
}
|
||||
});
|
67
fas/fas/static/javascript/style.js
Normal 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
|
@ -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
|
0
fas/fas/templates/__init__.py
Normal file
BIN
fas/fas/templates/__init__.pyc
Normal file
14
fas/fas/templates/autoComplete.kid
Normal 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>
|
19
fas/fas/templates/edit.kid
Normal 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>
|
236
fas/fas/templates/editAccount.kid
Normal 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>
|
BIN
fas/fas/templates/editAccount.pyc
Normal file
88
fas/fas/templates/editGroup.kid
Normal 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>
|
BIN
fas/fas/templates/editGroup.pyc
Normal file
10
fas/fas/templates/error.kid
Normal 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>
|
44
fas/fas/templates/groupList.kid
Normal 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>
|
BIN
fas/fas/templates/groupList.pyc
Normal file
32
fas/fas/templates/home.kid
Normal 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
40
fas/fas/templates/invite.kid
Normal 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} ≤<a href='mailto: ${user.mail}'>${user.mail}</a>≥ 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>
|