Initial revision
git-svn-id: svn://localhost/lasso-conform/trunk@2 2a3a78c3-912c-0410-af21-e1fb2d1df599
This commit is contained in:
parent
07c8faa4be
commit
ffbefbbfa4
|
@ -0,0 +1 @@
|
|||
lcs_cfg.py
|
|
@ -0,0 +1,3 @@
|
|||
APP_DIR = "/var/lib/lcs"
|
||||
DATA_DIR = "/usr/share/lcs"
|
||||
ERROR_LOG = None
|
|
@ -0,0 +1,10 @@
|
|||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
import qommon
|
||||
|
||||
import lasso
|
||||
if not hasattr(lasso, 'SAML2_SUPPORT'):
|
||||
lasso.SAML2_SUPPORT = False
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from root import RootDirectory, register_page
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
import random
|
||||
|
||||
from quixote import get_publisher, get_request, get_response, redirect
|
||||
from quixote.directory import Directory
|
||||
|
||||
from menu import *
|
||||
|
||||
import lcs
|
||||
from qommon import logger
|
||||
from qommon import misc
|
||||
from qommon.form import *
|
||||
from lcs.users import User
|
||||
from qommon import template
|
||||
|
||||
|
||||
class ByUserDirectory(Directory):
|
||||
def _q_lookup(self, component):
|
||||
return ByUserPages(component)
|
||||
|
||||
|
||||
class LoggerDirectory(Directory):
|
||||
_q_exports = ['', 'download', 'by_user']
|
||||
|
||||
by_user = ByUserDirectory()
|
||||
|
||||
def _q_index [html] (self):
|
||||
get_response().breadcrumb.append( ('logger/', _('Logs')) )
|
||||
html_top('logger', title = _('Logs'))
|
||||
request = get_request()
|
||||
logfile = request.get_field('logfile', 'lcs.log')
|
||||
if not logfile.startswith(str('lcs.log')) or str('/') in str(logfile):
|
||||
return template.error_page(_('Bad log file: %s') % logfile)
|
||||
logfilename = str(os.path.join(get_publisher().app_dir, logfile))
|
||||
|
||||
if not os.path.exists(logfilename):
|
||||
_('Nothing to show')
|
||||
else:
|
||||
if logfile:
|
||||
'<a href="download?logfile=%s">%s</a>' % (logfile, _('Download Raw Log File'))
|
||||
else:
|
||||
'<a href="download">%s</a>' % _('Download Raw Log File')
|
||||
|
||||
user_color_keys = {}
|
||||
last_date = None
|
||||
'<table id="logs">\n'
|
||||
'<thead> <tr>'
|
||||
' <th>%s</th>' % _('Time')
|
||||
' <th>%s</th>' % _('User')
|
||||
' <th>%s</th>' % _('Message')
|
||||
'<tr></thead>\n'
|
||||
'<tbody>\n'
|
||||
i = 1
|
||||
for line in file(logfilename):
|
||||
d = logger.readline(line)
|
||||
if not d:
|
||||
continue
|
||||
user_color_key = d['user_id']
|
||||
if user_color_key == 'anonymous':
|
||||
user_color_key += d['ip']
|
||||
if not user_color_keys.has_key(user_color_key):
|
||||
user_color_keys[user_color_key] = ''.join(
|
||||
['%x' % random.randint(0xc, 0xf) for x in range(3)])
|
||||
'<tr class="level-%s" style="background: #%s;">' % (
|
||||
d['level'].lower(), user_color_keys[user_color_key])
|
||||
if (last_date != d['date']):
|
||||
' <td class="time">%s %s</td>' % (d['date'], d['hour'][:-4])
|
||||
last_date = d['date']
|
||||
else:
|
||||
' <td class="time">%s</td>' % (d['hour'][:-4])
|
||||
if d['user_id'] == 'anonymous':
|
||||
userlabel = _('Anonymous')
|
||||
ip = d['ip']
|
||||
' <td class="userlabel"><span title="%s">%s</span></td>' % (ip, userlabel)
|
||||
elif d['user_id'] == 'unlogged':
|
||||
userlabel = _('Unlogged')
|
||||
ip = d['ip']
|
||||
' <td class="userlabel"><span title="%s">%s</span></td>' % (ip, userlabel)
|
||||
else:
|
||||
try:
|
||||
user = User.get(d['user_id'])
|
||||
except KeyError:
|
||||
userlabel = _('Unknown')
|
||||
else:
|
||||
userlabel = htmltext(user.name.replace(str(' '), str(' ')))
|
||||
' <td class="userlabel">%s</td>' % userlabel
|
||||
' <td class="message">%s</td>' % d['message']
|
||||
'</tr>\n'
|
||||
'</tbody>\n'
|
||||
'</table>\n'
|
||||
|
||||
logfiles = [x for x in os.listdir(get_publisher().app_dir) if x.startswith(str('lcs.log'))]
|
||||
if len(logfiles) > 1:
|
||||
options = []
|
||||
for lfile in logfiles:
|
||||
firstline = file(os.path.join(get_publisher().app_dir, lfile)).readline()
|
||||
d = logger.readline(firstline)
|
||||
if not d:
|
||||
continue
|
||||
if logfile == lfile:
|
||||
selected = 'selected="selected" '
|
||||
else:
|
||||
selected = ''
|
||||
options.append({'selected': selected, 'lfile': lfile,
|
||||
'date': '%s %s' % (d['date'], d['hour'])})
|
||||
|
||||
'<form id="other-log-select">'
|
||||
_('Select another logfile:')
|
||||
'<select name="logfile">'
|
||||
options.sort(lambda x,y: cmp(x['date'], y['date']))
|
||||
options.reverse()
|
||||
for option in options:
|
||||
option['since'] = str(_('Since: %s') % option['date'])[:-4]
|
||||
'<option value="%(lfile)s"%(selected)s>%(since)s</option>' % option
|
||||
'</select>'
|
||||
'<input type="submit" value="%s" />' % _('Submit')
|
||||
|
||||
|
||||
def download(self):
|
||||
request = get_request()
|
||||
logfile = request.get_field('logfile', 'lcs.log')
|
||||
if not logfile.startswith(str('lcs.log')) or str('/') in logfile:
|
||||
return template.error_page(_('Bad log file: %s') % logfile)
|
||||
logfilename = os.path.join(get_publisher().app_dir, logfile)
|
||||
response = get_response()
|
||||
response.set_content_type('text/x-log', 'iso-8859-1')
|
||||
response.set_header('content-disposition', 'attachment; filename=%s' % logfile)
|
||||
return open(logfilename).read()
|
||||
|
||||
|
||||
class ByUserPages(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def __init__(self, component):
|
||||
try:
|
||||
self.user = User.get(component)
|
||||
except KeyError:
|
||||
raise TraversalError()
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('logger', title = _('Logs'))
|
||||
'<h2>%s - %s</h2>' % (_('User'), self.user.name)
|
||||
|
||||
last_date = None
|
||||
'<table id="logs">'
|
||||
'<thead> <tr>'
|
||||
' <th>%s</th>' % _('Time')
|
||||
' <th>%s</th>' % _('Message')
|
||||
'<tr></thead>'
|
||||
'<tbody>'
|
||||
if os.path.exists(logger.logfile):
|
||||
for line in file(logger.logfile):
|
||||
d = logger.readline(line)
|
||||
if not d or d['user_id'] != str(self.user.id):
|
||||
continue
|
||||
'<tr>'
|
||||
if (last_date != d['date']):
|
||||
' <td class="time">%s %s</td>' % (d['date'], d['hour'][:-4])
|
||||
last_date = d['date']
|
||||
else:
|
||||
' <td class="time">%s</td>' % (d['hour'][:-4])
|
||||
' <td><a href="%s">%s</a></td>' % (d['url'], d['message'])
|
||||
'</tr>'
|
||||
'</tbody>'
|
||||
'</table>'
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from qommon.admin.menu import html_top
|
||||
|
||||
def error_page [html] (section, error):
|
||||
html_top(section, title = _('Error'))
|
||||
'<div id="error-page">'
|
||||
'<h2>%s</h2>' % _('Error')
|
||||
'<p>%s</p>' % error
|
||||
'</div>'
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import os
|
||||
|
||||
from quixote import get_session, get_publisher, get_request, get_response
|
||||
from quixote.directory import Directory, AccessControlled
|
||||
|
||||
import settings
|
||||
import users
|
||||
import logger
|
||||
|
||||
from qommon import errors
|
||||
|
||||
def gpl [html] ():
|
||||
"""<p>This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by the Free
|
||||
Software Foundation; either version 2 of the License, or (at your option)
|
||||
any later version.</p>
|
||||
|
||||
<p>This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
for more details.</p>
|
||||
|
||||
<p>You should have received a copy of the GNU General Public License along with
|
||||
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
|
||||
Place - Suite 330, Boston, MA 02111-1307, USA.</p>
|
||||
"""
|
||||
|
||||
|
||||
class RootDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['', 'settings', 'users', 'logger']
|
||||
|
||||
settings = settings.SettingsDirectory()
|
||||
users = users.UsersDirectory()
|
||||
logger = logger.LoggerDirectory()
|
||||
|
||||
items = [
|
||||
('users/', N_('Users')),
|
||||
('logger/', N_('Logs')),
|
||||
('settings/', N_('Settings')),
|
||||
('/', N_('Lasso Conformance Event Service Provider'))]
|
||||
|
||||
def _q_access(self):
|
||||
get_response().breadcrumb.append( ('admin/', _('Administration')) )
|
||||
|
||||
if os.path.exists(os.path.join(get_publisher().app_dir, 'ADMIN_FOR_ALL')):
|
||||
return
|
||||
session = get_session()
|
||||
|
||||
req = get_request()
|
||||
if req.user:
|
||||
if users.User.count() == 0:
|
||||
# this means user logged in anonymously
|
||||
pass
|
||||
elif not req.user.is_admin:
|
||||
raise errors.AccessForbiddenError()
|
||||
else:
|
||||
if users.User.count() > 0:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
|
||||
return
|
||||
|
||||
|
||||
def _q_index [html] (self):
|
||||
from menu import html_top
|
||||
html_top('/')
|
||||
'<blockquote>'
|
||||
gpl()
|
||||
'</blockquote>'
|
||||
|
||||
|
||||
def register_page(url_name, directory = None, label = None):
|
||||
first_time = False
|
||||
if not url_name in RootDirectory._q_exports:
|
||||
RootDirectory._q_exports.append(url_name)
|
||||
first_time = True
|
||||
|
||||
if directory:
|
||||
setattr(RootDirectory, url_name, directory)
|
||||
if not label:
|
||||
label = directory.label
|
||||
|
||||
if first_time:
|
||||
logger_index = RootDirectory.items.index(('logger/', 'Logs'))
|
||||
if directory:
|
||||
url_name += '/'
|
||||
RootDirectory.items.insert(logger_index, (url_name, label))
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
import cStringIO
|
||||
import cPickle
|
||||
import re
|
||||
import os
|
||||
import lasso
|
||||
import glob
|
||||
import zipfile
|
||||
import base64
|
||||
|
||||
from quixote import get_publisher, get_request, get_response, redirect
|
||||
from quixote.directory import Directory, AccessControlled
|
||||
|
||||
from menu import *
|
||||
|
||||
from qommon import misc, get_cfg
|
||||
from qommon.form import *
|
||||
|
||||
from qommon.admin.emails import EmailsDirectory
|
||||
import qommon.ident
|
||||
|
||||
def cfg_submit(form, cfg_key, fields):
|
||||
get_publisher().reload_cfg()
|
||||
cfg_key = str(cfg_key)
|
||||
cfg_dict = get_cfg(cfg_key, {})
|
||||
for k in fields:
|
||||
cfg_dict[str(k)] = form.get_widget(k).parse()
|
||||
get_publisher().cfg[cfg_key] = cfg_dict
|
||||
get_publisher().write_cfg()
|
||||
|
||||
class IdentificationDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def _q_index [html] (self):
|
||||
get_response().breadcrumb.append( ('identification/', _('Identification')) )
|
||||
identification_cfg = get_cfg('identification', {})
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(CheckboxesWidget, 'methods', title = _('Methods'),
|
||||
value = identification_cfg.get('methods'),
|
||||
elements = [
|
||||
('idp', _('Delegated to Liberty/SAML2 identity provider')),
|
||||
('password', _('Simple local username / password')),
|
||||
],
|
||||
inline = False)
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('..')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
html_top('settings', title = _('Identification'))
|
||||
'<h2>%s</h2>' % _('Identification')
|
||||
form.render()
|
||||
else:
|
||||
cfg_submit(form, 'identification', ['methods'])
|
||||
return redirect('..')
|
||||
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append( ('identification/', _('Identification')) )
|
||||
return qommon.ident.get_method_admin_directory(component)
|
||||
|
||||
|
||||
class SettingsDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['', 'themes', 'users',
|
||||
'template', 'emails', 'debug_options', 'language',
|
||||
'identification', 'sitename']
|
||||
|
||||
emails = EmailsDirectory()
|
||||
identification = IdentificationDirectory()
|
||||
|
||||
def _q_access(self):
|
||||
get_response().breadcrumb.append( ('settings/', _('Settings')) )
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('settings', title = _('Settings'))
|
||||
|
||||
'<dl> <dt><a href="identification/">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Identification'), _('Configure identification parameters'))
|
||||
|
||||
identification_cfg = get_cfg('identification', {})
|
||||
for method in identification_cfg.get('methods', []):
|
||||
try:
|
||||
method_admin = qommon.ident.get_method_admin_directory(method)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
'<dl> <dt><a href="identification/%s/">%s</a></dt> <dd>%s</dd>' % (
|
||||
method, _(method_admin.title), _(method_admin.label))
|
||||
|
||||
'<h2>%s</h2>' % _('Customisation')
|
||||
|
||||
'<dl>'
|
||||
'<dt><a href="sitename">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Site Name'), _('Configure site name'))
|
||||
'<dt><a href="language">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Language'), _('Configure site language'))
|
||||
'<dt><a href="themes">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Theme'), _('Configure theme'))
|
||||
'<dt><a href="template">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Template'), _('Configure template'))
|
||||
'<dt><a href="emails/">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Emails'), _('Configure email settings'))
|
||||
'</dl>'
|
||||
|
||||
'<h2>%s</h2>' % _('Misc')
|
||||
'<dl>'
|
||||
'<dt><a href="misc">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Misc'), _('Configure misc options'))
|
||||
'<dt><a href="debug_options">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Debug Options'), _('Configure options useful for debugging'))
|
||||
'</dl>'
|
||||
|
||||
'<h2>%s</h2>' % _('Import / Export')
|
||||
|
||||
'<dl>'
|
||||
'<dt><a href="import">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Import'), _('Import data from another site'))
|
||||
'<dt><a href="export">%s</a></dt> <dd>%s</dd>' % (
|
||||
_('Export'), _('Export data for another site'))
|
||||
'</dl>'
|
||||
|
||||
def themes [html] (self):
|
||||
import xml.dom.minidom
|
||||
|
||||
def getText(nodelist):
|
||||
rc = ""
|
||||
for node in nodelist:
|
||||
if node.nodeType == node.TEXT_NODE:
|
||||
rc = rc + node.data
|
||||
return rc.encode('iso-8859-1')
|
||||
|
||||
request = get_request()
|
||||
|
||||
if not request.form.has_key('theme'):
|
||||
current_theme = get_cfg('branding', {}).get('theme', 'default')
|
||||
|
||||
get_response().breadcrumb.append(('themes', _('Themes')))
|
||||
html_top('settings', title = _('Themes'))
|
||||
"<h2>%s</h2>" % _('Themes')
|
||||
'<form action="themes" enctype="multipart/form-data" method="post">'
|
||||
theme_files = glob.glob(os.path.join(get_publisher().DATA_DIR, str('themes/*/desc.xml')))
|
||||
theme_files.sort()
|
||||
'<ul class="biglist">'
|
||||
for t in theme_files:
|
||||
dom = xml.dom.minidom.parseString(open(t).read())
|
||||
theme = dom.getElementsByTagName('theme')[0].attributes['name'].value.encode(
|
||||
str('iso-8859-1'))
|
||||
if current_theme == theme:
|
||||
checked = ' checked="checked"'
|
||||
else:
|
||||
checked = ''
|
||||
'<li>'
|
||||
'<strong class="label">'
|
||||
' <input name="theme" value="%s" type="radio"%s>%s</input></strong>' % (
|
||||
theme, checked, getText(dom.getElementsByTagName('label')[0].childNodes))
|
||||
icon_file = t.replace(str('desc.xml'), str('icon.png'))
|
||||
if os.path.exists(icon_file):
|
||||
'<img src="/themes/%s/icon.png" alt="" class="theme-icon" />' % theme
|
||||
'<p class="details">%s<br/>by %s</p>' % (
|
||||
getText(dom.getElementsByTagName('desc')[0].childNodes),
|
||||
getText(dom.getElementsByTagName('author')[0].childNodes))
|
||||
'</li>'
|
||||
'</ul>'
|
||||
'<div class="buttons">'
|
||||
'<input type="submit" name="submit" value="%s" />' % _('Submit')
|
||||
'</div>'
|
||||
'</form>'
|
||||
else:
|
||||
if not misc.cfg.has_key('branding'):
|
||||
misc.cfg[str('branding')] = {}
|
||||
misc.cfg[str('branding')][str('theme')] = str(request.form['theme'])
|
||||
misc.write_cfg()
|
||||
return redirect('.')
|
||||
|
||||
|
||||
def template [html] (self):
|
||||
from qommon.template import DEFAULT_TEMPLATE_EZT
|
||||
branding_cfg = get_cfg('branding', {})
|
||||
template = branding_cfg.get('template', DEFAULT_TEMPLATE_EZT)
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.add(TextWidget, 'template', title = _('Site Template'), value = template,
|
||||
cols = 80, rows = 25)
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('template', _('Template')))
|
||||
html_top('settings', title = _('Template'))
|
||||
'<h2>%s</h2>' % _('Template')
|
||||
form.render()
|
||||
else:
|
||||
self.template_submit(form)
|
||||
redirect('.')
|
||||
|
||||
|
||||
def template_submit(self, form):
|
||||
get_publisher().reload_cfg()
|
||||
branding_cfg = get_cfg('branding', {})
|
||||
template = form.get_widget('template').parse()
|
||||
if template == DEFAULT_TEMPLATE_EZT or not template:
|
||||
if branding_cfg.has_key('template'):
|
||||
del branding_cfg['template']
|
||||
else:
|
||||
branding_cfg['template'] = template
|
||||
misc.cfg['branding'] = branding_cfg
|
||||
get_publisher().write_cfg()
|
||||
|
||||
|
||||
def debug_options [html] (self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
debug_cfg = get_cfg('debug', {})
|
||||
form.add(StringWidget, 'error_email', title = _('Email for Tracebacks'),
|
||||
value = debug_cfg.get('error_email', ''))
|
||||
form.add(SingleSelectWidget, 'display_exceptions', title = _('Display Exceptions'),
|
||||
value = debug_cfg.get('display_exceptions', ''),
|
||||
options = [ (str(''), _('No display')),
|
||||
(str('text'), _('Display as Text')),
|
||||
(str('text-in-html'), _('Display as Text in HTML an error page')),
|
||||
(str('html'), _('Display as HTML')) ])
|
||||
form.add(CheckboxWidget, 'logger', title = _('Logger'),
|
||||
value = debug_cfg.get('logger', False))
|
||||
form.add(CheckboxWidget, 'debug_mode', title = _('Enable debug mode'),
|
||||
value = debug_cfg.get('debug_mode', False))
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('debug_options', _('Debug Options')))
|
||||
html_top('settings', title = _('Debug Options'))
|
||||
'<h2>%s</h2>' % _('Debug Options')
|
||||
form.render()
|
||||
else:
|
||||
cfg_submit(form, 'debug', ('error_email', 'display_exceptions', 'logger', 'debug_mode'))
|
||||
redirect('.')
|
||||
|
||||
def language [html] (self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
language_cfg = get_cfg('language', {})
|
||||
form.add(SingleSelectWidget, 'language', title = _('Language'),
|
||||
value = language_cfg.get('language'),
|
||||
options = [ (None, _('System Default')),
|
||||
(str('HTTP'), _('From HTTP Accept-Language header')),
|
||||
(str('en'), _('English')) ] )
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('language', _('Language')))
|
||||
html_top('settings', title = _('Language'))
|
||||
'<h2>%s</h2>' % _('Language')
|
||||
form.render()
|
||||
else:
|
||||
cfg_submit(form, 'language', ['language'])
|
||||
redirect('.')
|
||||
|
||||
def sitename [html] (self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
misc_cfg = get_cfg('misc', {})
|
||||
form.add(StringWidget, 'sitename', title = _('Site Name'),
|
||||
value = get_cfg('sitename'))
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('sitename', _('Site Name')))
|
||||
html_top('settings', title = _('Site Name'))
|
||||
'<h2>%s</h2>' % _('Site Name')
|
||||
form.render()
|
||||
else:
|
||||
cfg_submit(form, 'misc', ['sitename'])
|
||||
redirect('.')
|
||||
|
|
@ -0,0 +1,255 @@
|
|||
import random
|
||||
import lasso
|
||||
|
||||
from quixote import get_response, get_request, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
|
||||
from menu import *
|
||||
|
||||
from qommon import emails
|
||||
from qommon import errors
|
||||
from qommon import misc, get_cfg
|
||||
from lcs.users import User
|
||||
|
||||
import qommon.ident
|
||||
from qommon.form import *
|
||||
|
||||
|
||||
class UserUI:
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
def form(self):
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
formdef = User.get_formdef()
|
||||
users_cfg = get_cfg('users', {})
|
||||
|
||||
form = Form(enctype='multipart/form-data')
|
||||
if not formdef or not users_cfg.get('field_name'):
|
||||
form.add(StringWidget, 'name', title = _('Name'), required = True, size=30,
|
||||
value = self.user.name)
|
||||
if not formdef or not users_cfg.get('field_email'):
|
||||
form.add(EmailWidget, 'email', title = _('Email'), required = False, size=30,
|
||||
value = self.user.email)
|
||||
if formdef:
|
||||
formdef.add_fields_to_form(form, form_data = self.user.form_data)
|
||||
form.add(CheckboxWidget, 'is_admin', title = _('Administrator Account'),
|
||||
value = self.user.is_admin)
|
||||
|
||||
for klass in [x for x in qommon.ident.get_method_classes() if x.key in ident_methods]:
|
||||
if klass.method_admin_widget:
|
||||
value = klass().get_value(self.user)
|
||||
form.add(klass.method_admin_widget, 'method_%s' % klass.key, required = True,
|
||||
value = value)
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
return form
|
||||
|
||||
def submit_form(self, form):
|
||||
formdef = User.get_formdef()
|
||||
if not self.user:
|
||||
self.user = User()
|
||||
for f in ('name', 'email', 'is_admin'):
|
||||
widget = form.get_widget(f)
|
||||
if widget:
|
||||
setattr(self.user, f, widget.parse())
|
||||
if formdef:
|
||||
data = formdef.get_data(form)
|
||||
users_cfg = get_cfg('users', {})
|
||||
if users_cfg.get('field_name'):
|
||||
self.user.name = data.get(users_cfg.get('field_name'))
|
||||
if users_cfg.get('field_email'):
|
||||
self.user.email = data.get(users_cfg.get('field_email'))
|
||||
self.user.form_data = data
|
||||
|
||||
# user is stored first so it get an id; necessary for some ident
|
||||
# methods
|
||||
self.user.store()
|
||||
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
for klass in [x for x in qommon.ident.get_method_classes() if x.key in ident_methods]:
|
||||
widget = form.get_widget('method_%s' % klass.key)
|
||||
if widget:
|
||||
klass().submit(self.user, widget)
|
||||
|
||||
# XXX: and store!
|
||||
# XXX 2: but pay attention to errors set on widget (think
|
||||
# "duplicate username") (and the calling method will also
|
||||
# have to check this)
|
||||
|
||||
|
||||
class UserPage(Directory):
|
||||
_q_exports = ['', 'edit', 'delete', 'token', 'debug']
|
||||
|
||||
def __init__(self, component):
|
||||
self.user = User.get(component)
|
||||
self.user_ui = UserUI(self.user)
|
||||
get_response().breadcrumb.append((component + '/', self.user.name))
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('users', '%s - %s' % (_('User'), self.user.name))
|
||||
'<h2>%s - %s</h2>' % (_('User'), self.user.name)
|
||||
'<div class="form">'
|
||||
'<div class="title">%s</div>' % _('Name')
|
||||
'<div class="StringWidget content">%s</div>' % self.user.name
|
||||
if self.user.email:
|
||||
'<div class="title">%s</div>' % _('Email')
|
||||
'<div class="StringWidget content">%s</div>' % self.user.email
|
||||
if self.user.is_admin:
|
||||
'<div class="title">%s</div>' % _('Roles')
|
||||
'<div class="StringWidget content"><ul>'
|
||||
if self.user.is_admin:
|
||||
'<li><strong>%s</strong></li>' % _('Site Administrator')
|
||||
'</ul></div>'
|
||||
if self.user.lasso_dump:
|
||||
identity = lasso.Identity.newFromDump(self.user.lasso_dump)
|
||||
server = misc.get_lasso_server()
|
||||
if len(identity.providerIds) and server:
|
||||
'<h3>%s</h3>' % _('Liberty Alliance Details')
|
||||
'<div class="StringWidget content"><ul>'
|
||||
for pid in identity.providerIds:
|
||||
provider = server.getProvider(pid)
|
||||
label = misc.get_provider_label(provider)
|
||||
if label:
|
||||
label = '%s (%s)' % (label, pid)
|
||||
else:
|
||||
label = pid
|
||||
federation = identity.getFederation(pid)
|
||||
'<li>'
|
||||
_('Account federated with %s') % label
|
||||
'<br />'
|
||||
if federation.localNameIdentifier:
|
||||
_("local: ") + federation.localNameIdentifier.content
|
||||
if federation.remoteNameIdentifier:
|
||||
_("remote: ") + federation.remoteNameIdentifier.content
|
||||
'</li>'
|
||||
'</ul></div>'
|
||||
|
||||
if get_cfg('debug', {}).get('debug_mode', False):
|
||||
'<h4>%s</h4>' % _('Lasso Identity Dump')
|
||||
'<pre>%s</pre>' % self.user.lasso_dump
|
||||
'</div>'
|
||||
|
||||
def debug [html] (self):
|
||||
get_response().breadcrumb.append( ('debug', _('Debug')) )
|
||||
html_top('users', 'Debug')
|
||||
"<h2>Debug - %s</h2>" % self.user.name
|
||||
"<pre>"
|
||||
self.user.lasso_dump
|
||||
"</pre>"
|
||||
|
||||
def edit [html] (self):
|
||||
form = self.user_ui.form()
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('..')
|
||||
|
||||
display_form = (not form.is_submitted() or form.has_errors())
|
||||
|
||||
if display_form:
|
||||
get_response().breadcrumb.append( ('edit', _('Edit')) )
|
||||
html_top('users', title = _('Edit User'))
|
||||
'<h2>%s</h2>' % _('Edit User')
|
||||
form.render()
|
||||
else:
|
||||
self.user_ui.submit_form(form)
|
||||
return redirect('..')
|
||||
|
||||
def delete [html] (self):
|
||||
form = Form(enctype="multipart/form-data")
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
|
||||
"You are about to irrevocably delete this user.")))
|
||||
form.add_submit("submit", _("Submit"))
|
||||
form.add_submit("cancel", _("Cancel"))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('..')
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('delete', _('Delete')))
|
||||
html_top('users', title = _('Delete User'))
|
||||
'<h2>%s %s</h2>' % (_('Deleting User:'), self.user.name)
|
||||
form.render()
|
||||
else:
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
for klass in [x for x in qommon.ident.get_method_classes() if x.key in ident_methods]:
|
||||
klass().delete(self.user)
|
||||
self.user.remove_self()
|
||||
return redirect('..')
|
||||
|
||||
|
||||
class UsersDirectory(Directory):
|
||||
_q_exports = ['', 'new']
|
||||
|
||||
def _q_index [html] (self):
|
||||
get_response().breadcrumb.append( ('users/', _('Users')) )
|
||||
html_top('users', title = _('Users'))
|
||||
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
if ident_methods == ['idp'] and len(get_cfg('idp', {}).items()) == 0:
|
||||
'<p>%s</p>' % _('Liberty support must be setup before creating users.')
|
||||
else:
|
||||
"""<ul id="nav-users-admin">
|
||||
<li><a href="new">%s</a></li>
|
||||
</ul>""" % (_('New User'))
|
||||
|
||||
debug_cfg = get_cfg('debug', {})
|
||||
|
||||
users = User.select(order_by = 'name')
|
||||
|
||||
'<ul class="biglist">'
|
||||
for user in users:
|
||||
'<li>'
|
||||
'<strong class="label">%s</strong>' % user.display_name
|
||||
if user.email:
|
||||
'<p class="details">'
|
||||
user.email
|
||||
'</p>'
|
||||
|
||||
'<p class="commands">'
|
||||
command_icon('%s/' % user.id, 'view')
|
||||
command_icon('%s/edit' % user.id, 'edit')
|
||||
command_icon('%s/delete' % user.id, 'remove')
|
||||
if debug_cfg.get('logger', False):
|
||||
command_icon('../logger/by_user/%s/' % user.id, 'logs',
|
||||
label = _('Logs'), icon = 'stock_harddisk_16.png')
|
||||
'</p></li>'
|
||||
'</ul>'
|
||||
|
||||
def new [html] (self):
|
||||
get_response().breadcrumb.append( ('users/', _('Users')) )
|
||||
get_response().breadcrumb.append( ('new', _('New')) )
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
if ident_methods == ['idp'] and len(get_cfg('idp', {}).items()) == 0:
|
||||
return error_page('users',
|
||||
_('Liberty support must be setup before creating users.'))
|
||||
# XXX: user must be logged in to get here
|
||||
user = User()
|
||||
user_ui = UserUI(user)
|
||||
first_user = User.count() == 0
|
||||
if first_user:
|
||||
user.is_admin = first_user
|
||||
form = user_ui.form()
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
html_top('users', title = _('New User'))
|
||||
'<h2>%s</h2>' % _('New User')
|
||||
form.render()
|
||||
else:
|
||||
user_ui.submit_form(form)
|
||||
if first_user:
|
||||
req = get_request()
|
||||
if req.user:
|
||||
user_ui.user.name_identifiers = req.user.name_identifiers
|
||||
user_ui.user.lasso_dump = req.user.lasso_dump
|
||||
user_ui.user.store()
|
||||
get_session().set_user(user_ui.user.id)
|
||||
return redirect('.')
|
||||
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append( ('users/', _('Users')) )
|
||||
try:
|
||||
return UserPage(component)
|
||||
except KeyError:
|
||||
raise errors.TraversalError()
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
from root import RootDirectory, register_directory, register_menu_item
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import quixote
|
||||
from quixote import get_response
|
||||
from quixote.html import htmltext
|
||||
|
||||
from qommon import get_cfg
|
||||
|
||||
from lcs.users import User
|
||||
|
||||
def error_page [html] (section, error):
|
||||
html_top(section, title = _('Error'))
|
||||
'<div id="error-page">'
|
||||
'<h2>%s</h2>' % _('Error')
|
||||
'<p>%s</p>' % error
|
||||
'</div>'
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import os
|
||||
import csv
|
||||
import cStringIO
|
||||
|
||||
from quixote import get_session, get_session_manager, get_publisher, get_request, get_response
|
||||
from quixote.directory import Directory, AccessControlled
|
||||
|
||||
from qommon.backoffice.menu import html_top
|
||||
|
||||
from qommon import misc, get_logger
|
||||
|
||||
from qommon import errors
|
||||
|
||||
from lcs.users import User
|
||||
|
||||
def ellipsize(s, length = 30):
|
||||
if not s or len(s) < length:
|
||||
return s
|
||||
return s[:length-5] + ' (...)'
|
||||
|
||||
|
||||
def register_directory(urlname, directory):
|
||||
if not urlname in RootDirectory._q_exports:
|
||||
RootDirectory._q_exports.append(urlname)
|
||||
setattr(RootDirectory, urlname, directory)
|
||||
|
||||
def register_menu_item(url, title):
|
||||
RootDirectory.items.append((url, title))
|
||||
|
||||
|
||||
class RootDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
items = [
|
||||
('/', N_('Lasso Conformance Event Service Provider'))]
|
||||
|
||||
def _q_access(self):
|
||||
user = get_request().user
|
||||
if not user:
|
||||
raise errors.AccessUnauthorizedError()
|
||||
|
||||
def _q_index [html] (self):
|
||||
html_top('', _('Back office'))
|
||||
'XXX'
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append( ('backoffice/', _('Back Office')) )
|
||||
return Directory._q_traverse(self, path)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import time
|
||||
import sys
|
||||
import os
|
||||
|
||||
import lcs
|
||||
from lcs import sessions
|
||||
|
||||
def clean_vhost_sessions():
|
||||
manager = sessions.StorageSessionManager()
|
||||
one_week_ago = time.time() - 2*86400
|
||||
one_month_ago = time.time() - 30*86400
|
||||
for session_key in manager.keys():
|
||||
try:
|
||||
session = manager.get(session_key)
|
||||
except AttributeError:
|
||||
del manager[session_key]
|
||||
continue
|
||||
if session._access_time < one_week_ago or session._creation_time < one_month_ago:
|
||||
del manager[session.id]
|
||||
|
||||
def clean_sessions(args):
|
||||
publisher = lcs.create_publisher()
|
||||
|
||||
if '--single_host' in args:
|
||||
clean_vhost_sessions()
|
||||
else:
|
||||
hostnames = os.listdir(lcs.APP_DIR)
|
||||
for hostname in hostnames:
|
||||
publisher.app_dir = os.path.join(lcs.APP_DIR, hostname)
|
||||
clean_vhost_sessions()
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import socket
|
||||
import sys
|
||||
from quixote.server.scgi_server import run
|
||||
|
||||
import publisher
|
||||
|
||||
def start(args):
|
||||
port = 3010
|
||||
single_host = False
|
||||
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i] == '--port':
|
||||
port = int(args[i+1])
|
||||
i += 1
|
||||
elif args[i] == '--extra':
|
||||
publisher.LcsPublisher.register_extra_dir(args[i+1])
|
||||
i += 1
|
||||
i += 1
|
||||
|
||||
try:
|
||||
run(publisher.LcsPublisher.create_publisher, port=port, script_name = '')
|
||||
except socket.error, e:
|
||||
if e[0] == 98:
|
||||
print >> sys.stderr, 'address already in use'
|
||||
sys.exit(1)
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
from qommon import get_cfg
|
||||
|
||||
import qommon.liberty
|
||||
|
||||
class LibertyDirectory(qommon.liberty.LibertyDirectory):
|
||||
|
||||
def lookup_user(self, session, login):
|
||||
ni = login.nameIdentifier.content
|
||||
session.name_identifier = ni
|
||||
nis = list(get_publisher().user_class.select(lambda x: ni in x.name_identifiers))
|
||||
if nis:
|
||||
user = nis[0]
|
||||
else:
|
||||
return None
|
||||
user.lasso_dump = login.identity.dump()
|
||||
user.store()
|
||||
return user
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import os
|
||||
|
||||
from Defaults import *
|
||||
|
||||
try:
|
||||
from lcs_cfg import *
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from qommon import set_publisher_class
|
||||
from qommon.publisher import QommonPublisher
|
||||
|
||||
from root import RootDirectory
|
||||
import sessions
|
||||
|
||||
from users import User
|
||||
|
||||
class LcsPublisher(QommonPublisher):
|
||||
APP_NAME = 'lcs'
|
||||
APP_DIR = APP_DIR
|
||||
DATA_DIR = DATA_DIR
|
||||
ERROR_LOG = ERROR_LOG
|
||||
|
||||
root_directory_class = RootDirectory
|
||||
session_manager_class = sessions.StorageSessionManager
|
||||
user_class = User
|
||||
|
||||
def get_backoffice(cls):
|
||||
import backoffice
|
||||
return backoffice
|
||||
get_backoffice = classmethod(get_backoffice)
|
||||
|
||||
def get_admin(cls):
|
||||
import admin
|
||||
return admin
|
||||
get_admin = classmethod(get_admin)
|
||||
|
||||
|
||||
set_publisher_class(LcsPublisher)
|
||||
LcsPublisher.register_extra_dir(os.path.join(os.path.dirname(__file__), 'extra'))
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
import os
|
||||
|
||||
from quixote import get_publisher, get_response, get_session, redirect, get_session_manager
|
||||
from quixote.directory import Directory
|
||||
from quixote.util import StaticDirectory
|
||||
|
||||
import admin
|
||||
import backoffice
|
||||
import liberty
|
||||
from qommon import saml2
|
||||
|
||||
from qommon import errors
|
||||
from qommon import logger
|
||||
from qommon import get_cfg
|
||||
from qommon import template
|
||||
from qommon.form import *
|
||||
import qommon.ident
|
||||
|
||||
from users import User
|
||||
|
||||
|
||||
class IdentDirectory(Directory):
|
||||
def _q_lookup(self, component):
|
||||
get_response().breadcrumb.append(('ident/', None))
|
||||
return qommon.ident.get_method_directory(component)
|
||||
|
||||
|
||||
class LoginDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def _q_index [html] (self):
|
||||
logger.info('login')
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
|
||||
if len(ident_methods) == 0:
|
||||
idps = get_cfg('idp', {})
|
||||
if len(idps) == 0:
|
||||
return template.error_page(_('Authentication subsystem is not yet configured.'))
|
||||
ident_methods = ['idp'] # fallback to old behaviour; liberty.
|
||||
|
||||
if len(ident_methods) == 1:
|
||||
method = ident_methods[0]
|
||||
return qommon.ident.login(method)
|
||||
else:
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(RadiobuttonsWidget, 'method',
|
||||
options = [(x.key, _(x.description)) \
|
||||
for x in qommon.ident.get_method_classes() if \
|
||||
x.key in ident_methods],
|
||||
delim = '<br/>')
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
method = form.get_widget('method').parse()
|
||||
if qommon.ident.base.ident_classes[method]().is_interactive():
|
||||
return redirect('../ident/%s/login' % method)
|
||||
else:
|
||||
return qommon.ident.login(method)
|
||||
else:
|
||||
template.html_top(_('Login'))
|
||||
'<p>%s</p>' % _('Select the identification method you want to use :')
|
||||
form.render()
|
||||
|
||||
class RegisterDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
|
||||
def _q_index [html] (self):
|
||||
logger.info('register')
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
|
||||
if len(ident_methods) == 0:
|
||||
idps = get_cfg('idp', {})
|
||||
if len(idps) == 0:
|
||||
return template.error_page(_('Authentication subsystem is not yet configured.'))
|
||||
ident_methods = ['idp'] # fallback to old behaviour; liberty.
|
||||
|
||||
if len(ident_methods) == 1:
|
||||
method = ident_methods[0]
|
||||
return qommon.ident.register(method)
|
||||
else:
|
||||
pass # XXX: register page when there is more than one ident method
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return qommon.ident.get_method_directory(component)
|
||||
|
||||
|
||||
class RootDirectory(Directory):
|
||||
_q_exports = ['', 'admin', 'backoffice', 'login', 'logout', 'liberty', 'saml',
|
||||
'ident', 'register']
|
||||
|
||||
def _q_index [html] (self):
|
||||
template.html_top('Lasso Conformance SP')
|
||||
|
||||
|
||||
def logout(self):
|
||||
logger.info('logout')
|
||||
session = get_session()
|
||||
if not session:
|
||||
return redirect('/')
|
||||
ident_methods = get_cfg('identification', {}).get('methods', [])
|
||||
if not 'idp' in ident_methods:
|
||||
get_session_manager().expire_session()
|
||||
return redirect('/')
|
||||
|
||||
# add settings to disable single logout?
|
||||
# (and to set it as none/get/soap?)
|
||||
return self.liberty.singleLogout()
|
||||
|
||||
def _q_traverse(self, path):
|
||||
session = get_session()
|
||||
if session:
|
||||
get_request().user = session.get_user()
|
||||
else:
|
||||
get_request().user = None
|
||||
|
||||
response = get_response()
|
||||
response.filter = {}
|
||||
if not hasattr(response, 'breadcrumb'):
|
||||
response.breadcrumb = [ ('', _('Home')) ]
|
||||
|
||||
return Directory._q_traverse(self, path)
|
||||
|
||||
def _q_lookup(self, component):
|
||||
if component == 'themes':
|
||||
dirname = os.path.join(get_publisher().data_dir, 'themes')
|
||||
return StaticDirectory(dirname, follow_symlinks = True)
|
||||
|
||||
raise errors.TraversalError()
|
||||
|
||||
admin = admin.RootDirectory()
|
||||
backoffice = backoffice.RootDirectory()
|
||||
saml = saml2.Saml2Directory()
|
||||
liberty = liberty.LibertyDirectory()
|
||||
login = LoginDirectory()
|
||||
register = RegisterDirectory()
|
||||
ident = IdentDirectory()
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#from quixote.session import Session
|
||||
from quixote.html import htmltext
|
||||
import users
|
||||
|
||||
import random
|
||||
|
||||
import qommon.sessions
|
||||
from qommon.sessions import Session
|
||||
|
||||
class BasicSession(Session):
|
||||
pass
|
||||
|
||||
qommon.sessions.BasicSession = BasicSession
|
||||
StorageSessionManager = qommon.sessions.StorageSessionManager
|
|
@ -0,0 +1,32 @@
|
|||
import os
|
||||
|
||||
from quixote import get_publisher
|
||||
|
||||
from qommon.storage import StorableObject
|
||||
|
||||
class User(StorableObject):
|
||||
_names = 'users'
|
||||
|
||||
name = None
|
||||
email = None
|
||||
is_admin = False
|
||||
anonymous = False
|
||||
|
||||
name_identifiers = None
|
||||
identification_token = None
|
||||
lasso_dump = None
|
||||
|
||||
def __init__(self, name = None):
|
||||
StorableObject.__init__(self)
|
||||
self.name = name
|
||||
self.name_identifiers = []
|
||||
self.roles = []
|
||||
|
||||
def get_display_name(self):
|
||||
if self.name:
|
||||
return self.name
|
||||
if self.email:
|
||||
return self.email
|
||||
return _('Unknown User')
|
||||
display_name = property(get_display_name)
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
def print_usage():
|
||||
print 'Usage: lcsctl.py command [...]'
|
||||
print ''
|
||||
print 'Commands:'
|
||||
print ' start start server'
|
||||
print ' clean_sessions clean old sessions'
|
||||
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print_usage()
|
||||
sys.exit(1)
|
||||
else:
|
||||
command = sys.argv[1]
|
||||
|
||||
if command == 'start':
|
||||
from lcs.ctl.start import start
|
||||
start(sys.argv[2:])
|
||||
elif command == 'clean_sessions':
|
||||
from lcs.ctl.clean_sessions import clean_sessions
|
||||
clean_sessions(sys.argv[2:])
|
||||
else:
|
||||
print_usage()
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
a {
|
||||
color: #028;
|
||||
}
|
||||
|
||||
div.content {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
div.TextWidget textarea,
|
||||
div.StringWidget input,
|
||||
div.IntWidget input,
|
||||
div.DateWidget input,
|
||||
div.WcsExtraStringWidget input,
|
||||
div.RegexStringWidget input,
|
||||
div.EmailWidget input,
|
||||
div.PasswordWidget input {
|
||||
border: 1px inset #ccc;
|
||||
margin: 1px;
|
||||
padding: 2px 3px;
|
||||
}
|
||||
|
||||
div.SingleSelectWidget select {
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
div.widget input.prefill-button {
|
||||
border: 1px outset #ccc;
|
||||
margin: 0 0 0 1em;
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
div.widget input.prefill-button:focus {
|
||||
border: 1px outset #ccc;
|
||||
margin: 0 0 0 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
div.widget textarea.readonly,
|
||||
div.widget input.readonly {
|
||||
border: 1px solid #ccc;
|
||||
background: #eee;
|
||||
margin: 0 0 0 1em;
|
||||
}
|
||||
|
||||
div.TextWidget textarea:focus,
|
||||
div.DateWidget input:focus,
|
||||
div.StringWidget input:focus,
|
||||
div.IntWidget input:focus,
|
||||
div.WcsExtraStringWidget input:focus,
|
||||
div.RegexStringWidget input:focus,
|
||||
div.EmailWidget input:focus,
|
||||
div.PasswordWidget input:focus {
|
||||
border: 2px solid #ccf;
|
||||
/*margin: 0px; */
|
||||
padding: 1px 2px;
|
||||
}
|
||||
|
||||
div.AccountSettingWidget label {
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
div.SubmitWidget input, input[type=submit] {
|
||||
margin-top: 1em;
|
||||
border: 1px outset #ccc;
|
||||
}
|
||||
|
||||
div.form .title, form.quixote .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.errornotice {
|
||||
background: #fd6;
|
||||
border: 1px solid #ffae15;
|
||||
margin: 0em 1em 1em 1em;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.infonotice {
|
||||
background: #7b95a6;
|
||||
border: 1px solid #153eaf;
|
||||
margin: 0em 1em 1em 1em;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
div.error {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
background: transparent url(warning.png) top left no-repeat;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
div.buttons div.SubmitWidget,
|
||||
div.buttons div.SubmitWidget div.content {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.buttons br { display: none; }
|
||||
|
||||
div.widget {
|
||||
margin-bottom: 0.5em;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
input[type="submit"][name="submit"] {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.form pre {
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
|
||||
div#error h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div#error {
|
||||
width: 40em;
|
||||
max-width: 500px;
|
||||
margin: 15% auto;
|
||||
background: white;
|
||||
border: 1px solid #999;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
div.hint {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
span.required {
|
||||
background: transparent url(required.png) 0px 0.5ex no-repeat;
|
||||
padding: 0 0 0 24px;
|
||||
margin-left: 1ex;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.buttons {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
div.RadiobuttonsWidget div.content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.error-page {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
pre#exception {
|
||||
overflow: scroll;
|
||||
padding: 1em;
|
||||
border: 1px solid #bbb;
|
||||
background: #f0f0f0;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div.StringWidget ul {
|
||||
margin: 0;
|
||||
padding-left: 2em;
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
div.inline-first div.title,
|
||||
div.inline div.title {
|
||||
display: inline;
|
||||
float: left;
|
||||
max-width: 20em;
|
||||
text-align: left;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
div.inline-first div.title span.required,
|
||||
div.inline div.title span.required {
|
||||
margin-left: 1ex;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
div.inline-first div.content,
|
||||
div.inline div.content {
|
||||
margin-left: 1ex;
|
||||
}
|
||||
|
||||
div.inline-first div.hint,
|
||||
div.inline div.hint {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.inline-first {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.inline {
|
||||
float: left;
|
||||
clear: none;
|
||||
}
|
||||
|
||||
div.inline-first div.content,
|
||||
div.inline div.content {
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.inline-first div.content,
|
||||
div.inline div.content {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.clear-both {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.CheckboxesWidget div.content ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.CheckboxesWidget div.content ul.inline li {
|
||||
display: inline;
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
div.dataview {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
div.dataview span.label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.dataview span.value {
|
||||
display: block;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
form div.page,
|
||||
div.dataview div.page {
|
||||
border: 1px solid #aaa;
|
||||
padding: 1ex;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
form div.page p,
|
||||
div.dataview div.page p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form div.page h3,
|
||||
div.dataview div.page h3 {
|
||||
margin: 0;
|
||||
margin-bottom: 1ex;
|
||||
}
|
||||
|
|
@ -0,0 +1,305 @@
|
|||
@import url(../common.css);
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
background: white url(page-bg.png) repeat-y;
|
||||
}
|
||||
|
||||
div#main-content {
|
||||
margin-left: 160px;
|
||||
margin-top: -10px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
div#main-content h1 {
|
||||
color: #006699;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
||||
div#main-content h2 {
|
||||
color: #006699;
|
||||
font-size: 115%;
|
||||
}
|
||||
|
||||
div#main-content h3 {
|
||||
color: #006699;
|
||||
font-size: 108%
|
||||
}
|
||||
|
||||
|
||||
|
||||
div#header {
|
||||
margin: 0;
|
||||
background: white url(head-bg.png) repeat-x;
|
||||
height: 58px;
|
||||
}
|
||||
|
||||
ul#menu {
|
||||
background: transparent url(head-logo.png) no-repeat;
|
||||
width: 177px;
|
||||
margin: 0;
|
||||
padding: 80px 0 0 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0066cc;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #ff9900;
|
||||
}
|
||||
|
||||
p.commands a {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
ul#menu a {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul#menu li.active a {
|
||||
border-bottom: 1px solid #ff9900;
|
||||
}
|
||||
|
||||
ul#menu li {
|
||||
font-size: 90%;
|
||||
margin-bottom: 1em;
|
||||
max-width: 130px;
|
||||
}
|
||||
|
||||
div#footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
ul.user-info {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
right: 25px;
|
||||
top: 13px;
|
||||
font-size: 70%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ul.user-info li {
|
||||
display: inline;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/** end of dc2 changes **/
|
||||
|
||||
|
||||
|
||||
ul.biglist {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul.biglist li {
|
||||
list-style-type: none;
|
||||
margin: 4px 0;
|
||||
padding: 0 2px;
|
||||
border: 1px solid #888;
|
||||
background: #ffe;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
ul.biglist li p.details {
|
||||
display: block;
|
||||
margin: 0;
|
||||
color: #555;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
|
||||
ul.biglist li p.commands {
|
||||
float: right;
|
||||
margin-top: -17px;
|
||||
}
|
||||
|
||||
ul.biglist li p.commands img {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
a img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
td.time {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
ul.biglist li.disabled, ul.biglist li.disabled p.details {
|
||||
color: #999;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
|
||||
dl dt {
|
||||
margin : 0;
|
||||
padding : 0 0 0 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin : 0.3em 0 1.5em 10px;
|
||||
}
|
||||
|
||||
|
||||
img.theme-icon {
|
||||
float: right;
|
||||
margin: -16px 4px 0px 3px;
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
div#new-field table {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#new-field div.widget {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#new-field div.buttons {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#new-field div.buttons input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#new-field {
|
||||
border: 1px solid #888;
|
||||
background: #ffe;
|
||||
margin: 2em 0 4px 0;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
div#new-field div.widget {
|
||||
}
|
||||
|
||||
div#new-field h3 {
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
div#new-field br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div#new-field p.commands {
|
||||
float: right;
|
||||
margin-top: -17px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
div.WorkflowStatusWidget {
|
||||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
p#breadcrumb {
|
||||
background: #e6e6e6;
|
||||
-moz-border-radius: 6px;
|
||||
padding: 3px 8px;
|
||||
font-size: 80%;
|
||||
border: 1px solid #bfbfbf;
|
||||
}
|
||||
|
||||
/** steps **/
|
||||
#steps {
|
||||
height: 32px;
|
||||
margin-bottom: 1em;
|
||||
background: #f0f0f0;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#steps ol {
|
||||
list-style: none;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#steps li {
|
||||
display: inline;
|
||||
padding-right: 1em;
|
||||
display: block;
|
||||
float: left;
|
||||
width: 30%;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#steps ol ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#steps span.marker {
|
||||
font-size: 26px;
|
||||
padding: 2px 9px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-align: center;
|
||||
background: #ddd;
|
||||
border: 1px solid #ddd;
|
||||
-moz-border-radius: 0.7ex;
|
||||
}
|
||||
|
||||
#steps li.current span.marker {
|
||||
background: #ffa500;
|
||||
border: 1px solid #ffc400;
|
||||
}
|
||||
|
||||
#steps span.label {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#steps li.current span.label {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#steps ol ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
/** logs **/
|
||||
form#other-log-select {
|
||||
margin-top: 2em;
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid #999;
|
||||
}
|
||||
|
||||
form#other-log-select select {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
tr.level-error td {
|
||||
border: 1px solid #800;
|
||||
background: red;
|
||||
}
|
||||
|
||||
tr.level-error td.message {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.stats {
|
||||
margin: 1ex 0;
|
||||
}
|
||||
|
||||
table.stats thead th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
table.stats td {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
form.inplaceeditor-form {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
form.inplaceeditor-form input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
|
@ -0,0 +1,318 @@
|
|||
@import url(common.css);
|
||||
/* derived from soFresh, a DotClear theme by Maurice Svay (GPL)
|
||||
* http://www.svay.com/files/soFresh/theme-sofresh-1.2.zip */
|
||||
|
||||
html, body {
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
background: #eee;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#page {
|
||||
width: 800px;
|
||||
margin: 2em auto;
|
||||
text-align: justify;
|
||||
background: white url(img/page.png) repeat-y;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#top {
|
||||
color: #09F;
|
||||
background: #FFF url(img/top.jpg) no-repeat;
|
||||
height: 100px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#top h1 {
|
||||
margin: 0;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
line-height: 100px;
|
||||
height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#footer {
|
||||
background: #FFF url(img/footer.jpg) no-repeat;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
div#main-content {
|
||||
margin: 0 2em;
|
||||
}
|
||||
|
||||
div#main-content h1 {
|
||||
color: #09F;
|
||||
}
|
||||
|
||||
#steps {
|
||||
height: 32px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
#steps ol {
|
||||
list-style: none;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
#steps li {
|
||||
display: inline;
|
||||
padding-right: 1em;
|
||||
display: block;
|
||||
float: left;
|
||||
width: 30%;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#steps ol ul {
|
||||
border: 1px solid #ffa500;
|
||||
margin-left: 2em;
|
||||
background: #ffdb94;
|
||||
margin-bottom: 1em;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#steps li li {
|
||||
display: block;
|
||||
width: auto;
|
||||
float: none;
|
||||
text-align: left;
|
||||
margin-left: 1em;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#steps li li.current {
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
#steps span.marker {
|
||||
font-size: 26px;
|
||||
padding: 2px 9px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-align: center;
|
||||
background: #ddd;
|
||||
border: 1px solid #ddd;
|
||||
-moz-border-radius: 0.7ex;
|
||||
}
|
||||
|
||||
#steps li.current span.marker {
|
||||
background: #ffa500;
|
||||
border: 1px solid #ffc400;
|
||||
}
|
||||
|
||||
#steps span.label {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
#steps li.current span.label {
|
||||
color: black;
|
||||
}
|
||||
|
||||
#steps {
|
||||
background: #f0f0f0;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#steps ol ul {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#steps ol li.current ul {
|
||||
display: block;
|
||||
}
|
||||
|
||||
form {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
p#receiver {
|
||||
margin: 0;
|
||||
margin-left: 2em;
|
||||
margin-top: -0.7em;
|
||||
margin-bottom: 1em;
|
||||
padding: 2px 5px;
|
||||
border: 1px solid #ccc;
|
||||
float: left;
|
||||
background: #ffe;
|
||||
}
|
||||
|
||||
p#login,
|
||||
p#logout {
|
||||
margin-top: 2em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
p#login a, p#logout a {
|
||||
text-decoration: underline;
|
||||
/*
|
||||
text-decoration: none;
|
||||
-moz-border-radius: 2em !important;
|
||||
padding: 1px 6px !important;
|
||||
border: 1px solid #ccc !important;
|
||||
border-bottom: 2px solid #999 !important;*/
|
||||
}
|
||||
|
||||
p#login a:hover {
|
||||
background-color: #ddeeff;
|
||||
}
|
||||
|
||||
h2#submitted, h2#done {
|
||||
margin-top: 2em;
|
||||
color: #09F;
|
||||
font-size: 130%;
|
||||
}
|
||||
|
||||
h2#done {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
ul li {
|
||||
list-style-image: url(img/li.png);
|
||||
}
|
||||
|
||||
h2, h3 {
|
||||
color: #09F;
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
table#listing {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
table#listing th {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #999;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
table#listing td {
|
||||
padding-right: 1ex;
|
||||
}
|
||||
|
||||
table#listing th a {
|
||||
text-decoration: none;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
table.sortable span.sortarrow {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
table#listing tr.status-new {
|
||||
background: #aea;
|
||||
}
|
||||
|
||||
table#listing tr.status-finished,
|
||||
table#listing tr.status-rejected {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
table#listing tr.status-rejected td {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
div.question p.label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
img.bar {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
div.question table {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
div.question table td {
|
||||
}
|
||||
|
||||
div.question table td.percent {
|
||||
text-align: right;
|
||||
padding: 0 1em;
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
div.question table td.label {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
span.user {
|
||||
margin-left: 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
a.listing {
|
||||
font-size: 80%;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #113;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #06C;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
#prelude {
|
||||
color: #aaa;
|
||||
background: transparent;
|
||||
text-align: right;
|
||||
position: relative;
|
||||
top: -85px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p#breadcrumb {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div.error-page {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
div.hint {
|
||||
display: inline;
|
||||
padding-left: 1ex;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
a.standalone {
|
||||
background: white url(img/h2.png) top left no-repeat;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
div#welcome-message a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
input.cancel {
|
||||
margin-left: 5em;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid #666;
|
||||
height: 1px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
div.buttons {
|
||||
clear: both;
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,36 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
import os
|
||||
import distutils.core
|
||||
from quixote.ptl.qx_distutils import qx_build_py
|
||||
|
||||
local_cfg = None
|
||||
if os.path.exists('lcs/lcs_cfg.py'):
|
||||
local_cfg = file('lcs/lcs_cfg.py').read()
|
||||
os.unlink('lcs/lcs_cfg.py')
|
||||
|
||||
def data_tree(destdir, sourcedir):
|
||||
extensions = ['.css', '.png', '.jpeg', '.jpg', '.xml', '.html', '.js']
|
||||
r = []
|
||||
for root, dirs, files in os.walk(sourcedir):
|
||||
l = [os.path.join(root, x) for x in files if os.path.splitext(x)[1] in extensions]
|
||||
r.append( (root.replace(sourcedir, destdir, 1), l) )
|
||||
if 'CVS' in dirs:
|
||||
dirs.remove('CVS')
|
||||
return r
|
||||
|
||||
distutils.core.setup(
|
||||
name = 'lcs',
|
||||
version = '0.0.0',
|
||||
maintainer = 'Frederic Peters',
|
||||
maintainer_email = 'fpeters@entrouvert.com',
|
||||
url = 'http://lasso.entrouvert.org',
|
||||
package_dir = { 'lcs': 'lcs' },
|
||||
packages = ['lcs', 'lcs.admin', 'lcs.backoffice', 'lcs.ctl', 'lcs.qommon'],
|
||||
cmdclass = {'build_py': qx_build_py},
|
||||
scripts = ['lcsctl.py'],
|
||||
data_files = data_tree('share/lcs/web/', 'root/')
|
||||
)
|
||||
|
||||
if local_cfg:
|
||||
file('lcs/lcs_cfg.py', 'w').write(local_cfg)
|
Reference in New Issue