diff --git a/lcs/.cvsignore b/lcs/.cvsignore new file mode 100644 index 0000000..0cf7e5c --- /dev/null +++ b/lcs/.cvsignore @@ -0,0 +1 @@ +lcs_cfg.py diff --git a/lcs/Defaults.py b/lcs/Defaults.py new file mode 100644 index 0000000..a209227 --- /dev/null +++ b/lcs/Defaults.py @@ -0,0 +1,3 @@ +APP_DIR = "/var/lib/lcs" +DATA_DIR = "/usr/share/lcs" +ERROR_LOG = None diff --git a/lcs/__init__.py b/lcs/__init__.py new file mode 100644 index 0000000..2e83e21 --- /dev/null +++ b/lcs/__init__.py @@ -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 + diff --git a/lcs/admin/__init__.py b/lcs/admin/__init__.py new file mode 100644 index 0000000..b76c2b9 --- /dev/null +++ b/lcs/admin/__init__.py @@ -0,0 +1,2 @@ +from root import RootDirectory, register_page + diff --git a/lcs/admin/logger.ptl b/lcs/admin/logger.ptl new file mode 100644 index 0000000..01c8648 --- /dev/null +++ b/lcs/admin/logger.ptl @@ -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: + '%s' % (logfile, _('Download Raw Log File')) + else: + '%s' % _('Download Raw Log File') + + user_color_keys = {} + last_date = None + '
%s | ' % _('Time') + '%s | ' % _('User') + '%s | ' % _('Message') + '||
---|---|---|---|---|
%s %s | ' % (d['date'], d['hour'][:-4]) + last_date = d['date'] + else: + '%s | ' % (d['hour'][:-4]) + if d['user_id'] == 'anonymous': + userlabel = _('Anonymous') + ip = d['ip'] + '%s | ' % (ip, userlabel) + elif d['user_id'] == 'unlogged': + userlabel = _('Unlogged') + ip = d['ip'] + '%s | ' % (ip, userlabel) + else: + try: + user = User.get(d['user_id']) + except KeyError: + userlabel = _('Unknown') + else: + userlabel = htmltext(user.name.replace(str(' '), str(' '))) + '%s | ' % userlabel + ' ' % d['message'] + '
%s' % self.user.lasso_dump + '
" + self.user.lasso_dump + "" + + 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')) + '
%s
' % _( + "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')) + '%s
' % _('Liberty support must be setup before creating users.') + else: + """ """ % (_('New User')) + + debug_cfg = get_cfg('debug', {}) + + users = User.select(order_by = 'name') + + '' + user.email + '
' + + '' + 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') + '
%s
' % error + '%s
' % _('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() + diff --git a/lcs/sessions.py b/lcs/sessions.py new file mode 100644 index 0000000..b726aa6 --- /dev/null +++ b/lcs/sessions.py @@ -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 diff --git a/lcs/users.py b/lcs/users.py new file mode 100644 index 0000000..a9b620d --- /dev/null +++ b/lcs/users.py @@ -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) + diff --git a/lcsctl.py b/lcsctl.py new file mode 100755 index 0000000..f70d81a --- /dev/null +++ b/lcsctl.py @@ -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() + diff --git a/root/css/common.css b/root/css/common.css new file mode 100644 index 0000000..deed4d0 --- /dev/null +++ b/root/css/common.css @@ -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; +} + diff --git a/root/css/dc2/admin.css b/root/css/dc2/admin.css new file mode 100644 index 0000000..dde1419 --- /dev/null +++ b/root/css/dc2/admin.css @@ -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; +} + diff --git a/root/css/dc2/head-bg.png b/root/css/dc2/head-bg.png new file mode 100644 index 0000000..cfad198 Binary files /dev/null and b/root/css/dc2/head-bg.png differ diff --git a/root/css/dc2/head-logo-empty.png b/root/css/dc2/head-logo-empty.png new file mode 100644 index 0000000..684e8f8 Binary files /dev/null and b/root/css/dc2/head-logo-empty.png differ diff --git a/root/css/dc2/head-logo.png b/root/css/dc2/head-logo.png new file mode 100644 index 0000000..684e8f8 Binary files /dev/null and b/root/css/dc2/head-logo.png differ diff --git a/root/css/dc2/page-bg.png b/root/css/dc2/page-bg.png new file mode 100644 index 0000000..0c1756b Binary files /dev/null and b/root/css/dc2/page-bg.png differ diff --git a/root/css/ico_user.png b/root/css/ico_user.png new file mode 100644 index 0000000..764f505 Binary files /dev/null and b/root/css/ico_user.png differ diff --git a/root/css/img/bulle.png b/root/css/img/bulle.png new file mode 100644 index 0000000..83e856d Binary files /dev/null and b/root/css/img/bulle.png differ diff --git a/root/css/img/day-date.png b/root/css/img/day-date.png new file mode 100644 index 0000000..e66f360 Binary files /dev/null and b/root/css/img/day-date.png differ diff --git a/root/css/img/footer.jpg b/root/css/img/footer.jpg new file mode 100644 index 0000000..2c7a79a Binary files /dev/null and b/root/css/img/footer.jpg differ diff --git a/root/css/img/h2.png b/root/css/img/h2.png new file mode 100644 index 0000000..708a113 Binary files /dev/null and b/root/css/img/h2.png differ diff --git a/root/css/img/li.png b/root/css/img/li.png new file mode 100644 index 0000000..e5aa456 Binary files /dev/null and b/root/css/img/li.png differ diff --git a/root/css/img/linkscat.png b/root/css/img/linkscat.png new file mode 100644 index 0000000..f20debc Binary files /dev/null and b/root/css/img/linkscat.png differ diff --git a/root/css/img/page.png b/root/css/img/page.png new file mode 100644 index 0000000..bad8ca5 Binary files /dev/null and b/root/css/img/page.png differ diff --git a/root/css/img/search.png b/root/css/img/search.png new file mode 100644 index 0000000..b8836f8 Binary files /dev/null and b/root/css/img/search.png differ diff --git a/root/css/img/sidebarh2.png b/root/css/img/sidebarh2.png new file mode 100644 index 0000000..97c0f8e Binary files /dev/null and b/root/css/img/sidebarh2.png differ diff --git a/root/css/img/top.jpg b/root/css/img/top.jpg new file mode 100644 index 0000000..1a4418f Binary files /dev/null and b/root/css/img/top.jpg differ diff --git a/root/css/img/top.png b/root/css/img/top.png new file mode 100644 index 0000000..b211101 Binary files /dev/null and b/root/css/img/top.png differ diff --git a/root/css/lcs.css b/root/css/lcs.css new file mode 100644 index 0000000..d38cd44 --- /dev/null +++ b/root/css/lcs.css @@ -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; +} + diff --git a/root/css/required.png b/root/css/required.png new file mode 100644 index 0000000..f9ad758 Binary files /dev/null and b/root/css/required.png differ diff --git a/root/css/warning.png b/root/css/warning.png new file mode 100644 index 0000000..6ec364d Binary files /dev/null and b/root/css/warning.png differ diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..441939e --- /dev/null +++ b/setup.py @@ -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)