Merge
@@ -115,3 +115,7 @@ class Alias(models.Model):
|
||||
permissions = (
|
||||
("view_aliases", "View aliases"),
|
||||
)
|
||||
|
||||
class Parameter(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
value = models.CharField(max_length=255)
|
||||
|
||||
@@ -46,9 +46,40 @@ def domain_menu(domain_id, selection, perms):
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def settings_menu(selection):
|
||||
return render_to_string('admin/settings_menu.html',
|
||||
{"selection" : selection})
|
||||
def settings_menu(selection, perms):
|
||||
entries = [
|
||||
{"name" : "permissions",
|
||||
"url" : reverse(admin.views.settings),
|
||||
"label" : _("Permissions"),
|
||||
"img" : "/static/pics/permissions.png"},
|
||||
{"name" : "addperm",
|
||||
"url" : reverse(admin.views.addpermission),
|
||||
"img" : "/static/pics/add.png",
|
||||
"label" : _("Add permission"),
|
||||
"class" : "boxed",
|
||||
"rel" : "{handler:'iframe',size:{x:320,y:210}}"},
|
||||
{"name" : "parameters",
|
||||
"url" : reverse(admin.views.viewparameters),
|
||||
"img" : "/static/pics/domains.png",
|
||||
"label" : _("Parameters")},
|
||||
]
|
||||
return render_to_string('main/menu.html',
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def domains_menu(selection, perms):
|
||||
entries = [
|
||||
{"name" : "domains",
|
||||
"url" : reverse(admin.views.domains),
|
||||
"label" : _("Domains"),
|
||||
"img" : "/static/pics/domains.png"}
|
||||
]
|
||||
entries += events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box",
|
||||
perms=perms)
|
||||
return render_to_string('main/menulist.html',
|
||||
{"menu" : entries, "selection" : selection,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def loadadminextmenu(perms):
|
||||
@@ -57,6 +88,46 @@ def loadadminextmenu(perms):
|
||||
return render_to_string('main/menulist.html',
|
||||
{"menu" : menu, "perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def param(app, definition):
|
||||
result = """<div class='row'>
|
||||
<label>%s</label>""" % definition["name"]
|
||||
name = "%s.%s" % (app, definition["name"])
|
||||
if definition["type"] in ["string", "int"]:
|
||||
value = definition.has_key("value") \
|
||||
and definition["value"] or definition["default"]
|
||||
result += """
|
||||
<input type='text' name='%s' id='%s' value='%s' />""" % (name, name, value)
|
||||
if definition["type"] in ["list", "list_yesno"]:
|
||||
result += """
|
||||
<select name='%s' id='%s'>""" % (name, name)
|
||||
values = []
|
||||
if definition["type"] == "list_yesno":
|
||||
values = [("yes", _("Yes")), ("no", _("No"))]
|
||||
else:
|
||||
if definition.has_key("values"):
|
||||
values = definition["values"]
|
||||
value = definition.has_key("value") \
|
||||
and definition["value"] or definition["default"]
|
||||
for v in values:
|
||||
selected = ""
|
||||
if value == v[0]:
|
||||
selected = " selected='selected'"
|
||||
result += "<option value='%s'%s>%s</option>\n" % (v[0], selected, v[1])
|
||||
result += """
|
||||
</select>
|
||||
"""
|
||||
|
||||
if definition.has_key("help"):
|
||||
result += """
|
||||
<a href='%s' onclick='return false;' class='Tips' title='%s'>
|
||||
<img src='/static/pics/info.png' border='0' />
|
||||
</a>""" % (definition["help"], _("Help"))
|
||||
result += """
|
||||
</div>
|
||||
"""
|
||||
return result
|
||||
|
||||
@register.filter
|
||||
def gender(value, target):
|
||||
if genders.has_key(value):
|
||||
|
||||
@@ -18,5 +18,7 @@ urlpatterns = patterns('mailng.admin.views',
|
||||
(r'^domains/(?P<dom_id>\d+)/delalias/(?P<alias_id>\d+)/$', 'delalias'),
|
||||
(r'^settings/$', 'settings'),
|
||||
(r'^settings/addperm/$', 'addpermission'),
|
||||
(r'^settings/delperm/(?P<mbox_id>\d+)/(?P<group>\w+)/$', 'deletepermission')
|
||||
(r'^settings/delperm/(?P<mbox_id>\d+)/(?P<group>\w+)/$', 'deletepermission'),
|
||||
(r'^settings/parameters/$', 'viewparameters'),
|
||||
(r'^settings/parameters/save/$', 'saveparameters'),
|
||||
)
|
||||
|
||||
@@ -12,11 +12,11 @@ from django.contrib.auth.decorators \
|
||||
import login_required, permission_required, user_passes_test
|
||||
from django.contrib.auth.models import User, Group
|
||||
from mailng import admin, main
|
||||
from mailng.admin.models import Domain, Mailbox, Alias
|
||||
from mailng.admin.models import Domain, Mailbox, Alias, Parameter
|
||||
from forms import MailboxForm, DomainForm, AliasForm, PermissionForm
|
||||
from mailng.lib.authbackends import crypt_password
|
||||
from mailng.lib import _render, _ctx_ok, _ctx_ko
|
||||
from mailng.lib import events
|
||||
from mailng.lib import events, parameters
|
||||
import string
|
||||
import copy
|
||||
import pwd
|
||||
@@ -51,7 +51,7 @@ def domains(request):
|
||||
return mailboxes(request, dom_id=mb.domain.id)
|
||||
|
||||
return HttpResponseRedirect(reverse(main.views.index))
|
||||
|
||||
|
||||
domains = Domain.objects.all()
|
||||
counters = {}
|
||||
for dom in domains:
|
||||
@@ -408,3 +408,33 @@ def deletepermission(request, mbox_id, group):
|
||||
mbox.user.groups.remove(grp)
|
||||
mbox.user.save()
|
||||
return HttpResponseRedirect(reverse(admin.views.settings))
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def viewparameters(request):
|
||||
apps = sorted(parameters._params.keys())
|
||||
gparams = []
|
||||
for app in apps:
|
||||
tmp = {"name" : app, "params" : []}
|
||||
for p in sorted(parameters._params[app]):
|
||||
tmp["params"] += [{"name" : p,
|
||||
"value" : parameters.get(app, p),
|
||||
"help" : parameters._params[app][p]["help"],
|
||||
"default" : parameters._params[app][p]["default"],
|
||||
"type" : parameters._params[app][p]["type"]}]
|
||||
gparams += [tmp]
|
||||
|
||||
return _render(request, 'admin/parameters.html', {
|
||||
"gparams" : gparams
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def saveparameters(request):
|
||||
for pname, v in request.POST.iteritems():
|
||||
if pname == "update":
|
||||
continue
|
||||
app, name = pname.split('.')
|
||||
parameters.save(app, name, v)
|
||||
request.user.message_set.create(message=_("Configuration saved."))
|
||||
return HttpResponseRedirect(reverse(admin.views.viewparameters))
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField(label=_("Username"))
|
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput,
|
||||
help_text=_("Password used to authenticate"))
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
This file demonstrates two different styles of tests (one doctest and one
|
||||
unittest). These will both pass when you run "manage.py test".
|
||||
|
||||
Replace these with more appropriate tests for your application.
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
class SimpleTest(TestCase):
|
||||
def test_basic_addition(self):
|
||||
"""
|
||||
Tests that 1 + 1 always equals 2.
|
||||
"""
|
||||
self.failUnlessEqual(1 + 1, 2)
|
||||
|
||||
__test__ = {"doctest": """
|
||||
Another way to test that 1 + 1 is equal to 2.
|
||||
|
||||
>>> 1 + 1 == 2
|
||||
True
|
||||
"""}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import events
|
||||
import mailng.admin
|
||||
from forms import LoginForm
|
||||
|
||||
def dologin(request):
|
||||
error = None
|
||||
if request.method == "POST":
|
||||
form = LoginForm(request.POST)
|
||||
user = authenticate(username=request.POST["username"],
|
||||
password=request.POST["password"])
|
||||
if user:
|
||||
if user.is_active:
|
||||
login(request, user)
|
||||
|
||||
events.raiseEvent("UserLogin", request=request,
|
||||
username=request.POST["username"],
|
||||
password=request.POST["password"])
|
||||
|
||||
next = request.POST["next"]
|
||||
if next == "None":
|
||||
next = reverse(mailng.admin.views.domains)
|
||||
return HttpResponseRedirect(next)
|
||||
else:
|
||||
error = _("Account is disabled.")
|
||||
error = _("Your username and password didn't match. Please try again.")
|
||||
else:
|
||||
form = LoginForm()
|
||||
next = request.GET.has_key("next") and request.GET["next"] or None
|
||||
return render_to_response("registration/login.html", {
|
||||
"form" : form, "error" : error, "next" : next
|
||||
})
|
||||
@@ -8,6 +8,7 @@ import email
|
||||
import re
|
||||
import os
|
||||
from django.http import HttpResponseRedirect, HttpResponse
|
||||
from django.template import Template, Context
|
||||
from django.utils import simplejson
|
||||
from django.conf.urls.defaults import *
|
||||
from django.utils.translation import ugettext as _, ungettext
|
||||
@@ -15,10 +16,11 @@ from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators \
|
||||
import login_required
|
||||
from mailng.lib import events, parameters
|
||||
from mailng.lib import _render, _ctx_ok, _ctx_ko, decode
|
||||
from mailng.lib import _render, _ctx_ok, _ctx_ko, decode, getctx
|
||||
from mailng.lib import db
|
||||
from mailng.admin.models import Mailbox
|
||||
from lib import AMrelease
|
||||
from templatetags.amextras import *
|
||||
from sql_listing import *
|
||||
|
||||
def init():
|
||||
@@ -40,21 +42,71 @@ def menu(**kwargs):
|
||||
return []
|
||||
|
||||
@login_required
|
||||
def index(request, message=None):
|
||||
start = time.time()
|
||||
def _listing(request, internal=False, filter=None):
|
||||
#start = time.time()
|
||||
if not request.user.is_superuser:
|
||||
mb = Mailbox.objects.get(user=request.user.id)
|
||||
filter = ["&maddr.email='%s'" % mb.full_address]
|
||||
if filter is None:
|
||||
filter = ["&maddr.email='%s'" % mb.full_address]
|
||||
else:
|
||||
filter += ["&maddr.email='%s'" % mb.full_address]
|
||||
pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1
|
||||
request.session["page"] = pageid # ?? nécessaire
|
||||
lst = SQLlisting(filter, baseurl="listing/", empty=internal)
|
||||
if internal:
|
||||
return lst.render(request, pageid=int(pageid))
|
||||
page = lst.paginator.getpage(pageid)
|
||||
if page:
|
||||
content = lst.fetch(request, page.id_start, page.id_stop)
|
||||
navbar = lst.render_navbar(page)
|
||||
else:
|
||||
filter = None
|
||||
try:
|
||||
pageid = request.GET["page"]
|
||||
if pageid == "":
|
||||
pageid = 1
|
||||
except KeyError:
|
||||
pageid = 1
|
||||
lst = SQLlisting(filter)
|
||||
return lst.render(request, pageid=int(pageid), start=start)
|
||||
content = _("Empty quarantine")
|
||||
navbar = ""
|
||||
ctx = getctx("ok", listing=content, navbar=navbar,
|
||||
menu=quar_menu("", request.user.get_all_permissions()))
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def index(request, message=None):
|
||||
return _listing(request, True)
|
||||
|
||||
# @login_required
|
||||
# def getmailcontent(request, mail_id):
|
||||
# from mailng.lib.email_listing import Email
|
||||
|
||||
# conn = db.getconnection("amavis_quarantine")
|
||||
# status, cursor = db.execute(conn, """
|
||||
# SELECT mail_text
|
||||
# FROM quarantine
|
||||
# WHERE quarantine.mail_id='%s'
|
||||
# """ % mail_id)
|
||||
# content = ""
|
||||
# for part in cursor.fetchall():
|
||||
# content += part[0]
|
||||
# msg = email.message_from_string(content)
|
||||
# links = request.GET.has_key("links") and request.GET["links"] or "0"
|
||||
# mode = request.GET.has_key("mode") and request.GET["mode"] or "plain"
|
||||
# mail = Email(msg, mode, links)
|
||||
# return mail.render(request)
|
||||
|
||||
# @login_required
|
||||
# def viewmail(request, mail_id):
|
||||
# conn = db.getconnection("amavis_quarantine")
|
||||
# status, cursor = db.execute(conn, """
|
||||
# SELECT mail_text
|
||||
# FROM quarantine
|
||||
# WHERE quarantine.mail_id='%s'
|
||||
# """ % mail_id)
|
||||
# content = ""
|
||||
# for part in cursor.fetchall():
|
||||
# content += part[0]
|
||||
# msg = email.message_from_string(content)
|
||||
# ctx = getctx("ok", )
|
||||
# return _render(request, 'amavis_quarantine/viewmail.html', {
|
||||
# "headers" : msg, "alert" : msg.get("X-Amavis-Alert"),
|
||||
# "mail_id" : mail_id, "page_id" : request.session["page"],
|
||||
# "params" : request.GET
|
||||
# })
|
||||
|
||||
@login_required
|
||||
def getmailcontent(request, mail_id):
|
||||
@@ -73,25 +125,25 @@ WHERE quarantine.mail_id='%s'
|
||||
links = request.GET.has_key("links") and request.GET["links"] or "0"
|
||||
mode = request.GET.has_key("mode") and request.GET["mode"] or "plain"
|
||||
mail = Email(msg, mode, links)
|
||||
return mail.render(request)
|
||||
|
||||
return _render(request, "common/viewmail.html", {
|
||||
"headers" : "",
|
||||
"mailbody" : mail.body,
|
||||
"pre" : mail.pre
|
||||
})
|
||||
|
||||
@login_required
|
||||
def viewmail(request, mail_id):
|
||||
conn = db.getconnection("amavis_quarantine")
|
||||
status, cursor = db.execute(conn, """
|
||||
SELECT mail_text
|
||||
FROM quarantine
|
||||
WHERE quarantine.mail_id='%s'
|
||||
""" % mail_id)
|
||||
content = ""
|
||||
for part in cursor.fetchall():
|
||||
content += part[0]
|
||||
msg = email.message_from_string(content)
|
||||
return _render(request, 'amavis_quarantine/viewmail.html', {
|
||||
"headers" : msg, "alert" : msg.get("X-Amavis-Alert"),
|
||||
"mail_id" : mail_id, "page_id" : request.GET["page"],
|
||||
"params" : request.GET
|
||||
})
|
||||
args = ""
|
||||
for kw in ["mode", "links"]:
|
||||
if kw in request.GET.keys():
|
||||
args += args != "" and "&" or "?"
|
||||
args += "%s=%s" % (kw, request.GET[kw])
|
||||
content = Template("""
|
||||
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
|
||||
""").render(Context({"url" : reverse(getmailcontent, args=[mail_id]) + args}))
|
||||
menu = viewm_menu("", request.session, mail_id, request.user.get_all_permissions())
|
||||
ctx = getctx("ok", menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def viewheaders(request, mail_id):
|
||||
@@ -129,7 +181,9 @@ def delete(request, mail_id, count=1):
|
||||
count) % {"count" : count}
|
||||
else:
|
||||
message = error
|
||||
return _redirect_to_index(request, message, count)
|
||||
ctx = getctx("ok", url="?page=%s" % request.session["page"], message=message)
|
||||
return HttpResponse(simplejson.dumps(ctx),
|
||||
mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def release(request, mail_id, count=1):
|
||||
@@ -152,13 +206,6 @@ WHERE msgrcpt.mail_id=msgs.mail_id AND msgrcpt.rid=maddr.id AND msgs.mail_id IN
|
||||
error = None
|
||||
for k, values in emails.iteritems():
|
||||
result = amr.sendreq(k, values["secret"], *values["rcpts"])
|
||||
# cmd = "/tmp/amavisd-release "
|
||||
# cmd += "%s %s" % (k, values["secret"])
|
||||
# for rcpt in values["rcpts"]:
|
||||
# cmd += " %s" % rcpt
|
||||
# cmd += "\n"
|
||||
# result = os.system(cmd)
|
||||
# if re.search("250 [^\s]+ Ok", result):
|
||||
if result:
|
||||
count += 1
|
||||
else:
|
||||
@@ -170,7 +217,9 @@ WHERE msgrcpt.mail_id=msgs.mail_id AND msgrcpt.rid=maddr.id AND msgs.mail_id IN
|
||||
count) % {"count" : count}
|
||||
else:
|
||||
message = error
|
||||
return _redirect_to_index(request, message, count)
|
||||
ctx = getctx("ok", url="?page=%s" % request.session["page"], message=message)
|
||||
return HttpResponse(simplejson.dumps(ctx),
|
||||
mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def process(request):
|
||||
@@ -182,19 +231,13 @@ def process(request):
|
||||
ids += "'%s'" % id
|
||||
if ids == "":
|
||||
return HttpResponseRedirect(reverse(index))
|
||||
|
||||
print "Ok c pas vide"
|
||||
if request.POST["action"] == "release":
|
||||
release(request, ids, count)
|
||||
return release(request, ids, count)
|
||||
|
||||
if request.POST["action"] == "delete":
|
||||
delete(request, ids, count)
|
||||
|
||||
try:
|
||||
pagenum = int(request.POST.get('pagenum', '1'))
|
||||
except ValueError:
|
||||
pagenum = 1
|
||||
ctx = _ctx_ok(reverse(index) + "?page=%s" % pagenum)
|
||||
return HttpResponse(simplejson.dumps(ctx),
|
||||
mimetype="application/json")
|
||||
return delete(request, ids, count)
|
||||
|
||||
@login_required
|
||||
def search(request):
|
||||
@@ -211,19 +254,16 @@ def search(request):
|
||||
filter += " OR "
|
||||
filter += "msgs.%s LIKE '%%%s%%'" % (c, pattern)
|
||||
filter = "&(%s)" % filter
|
||||
lst = SQLlisting([filter])
|
||||
try:
|
||||
pageid = request.GET["page"]
|
||||
if pageid == "":
|
||||
pageid = 1
|
||||
except KeyError:
|
||||
pageid = 1
|
||||
page = lst.paginator.getpage(pageid)
|
||||
if page:
|
||||
content = lst.fetch(page.id_start, page.id_stop).render(request)
|
||||
navbar = lst.render_navbar(page)
|
||||
else:
|
||||
content = "Empty quarantine"
|
||||
navbar = ""
|
||||
ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
return _listing(request, filter=filter)
|
||||
|
||||
# lst = SQLlisting([filter])
|
||||
|
||||
# page = lst.paginator.getpage(1)
|
||||
# if page:
|
||||
# content = lst.fetch(page.id_start, page.id_stop).render(request)
|
||||
# navbar = lst.render_navbar(page)
|
||||
# else:
|
||||
# content = "Empty quarantine"
|
||||
# navbar = ""
|
||||
# ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
|
||||
# return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import tables, db
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing
|
||||
|
||||
class Qtable(tables.Table):
|
||||
tableid = "emails"
|
||||
idkey = "mailid"
|
||||
selection = tables.SelectionColumn("selection", first=True)
|
||||
type = tables.Column("type", label=_("Type"))
|
||||
from_ = tables.Column("from", label=_("From"))
|
||||
subject = tables.Column("subject", label=_("Subject"))
|
||||
time = tables.Column("date", label=_("Date"))
|
||||
|
||||
def parse_date(self, value):
|
||||
return datetime.fromtimestamp(value)
|
||||
|
||||
def parse(self, header, value):
|
||||
try:
|
||||
return getattr(self, "parse_%s" % header)(value)
|
||||
except AttributeError:
|
||||
return value
|
||||
|
||||
class SQLconnector(MBconnector):
|
||||
def __init__(self, filter=None):
|
||||
self.conn = db.getconnection("amavis_quarantine")
|
||||
self.filter = ""
|
||||
if filter:
|
||||
for a in filter:
|
||||
if not a:
|
||||
continue
|
||||
if a[0] == "&":
|
||||
self.filter += " AND "
|
||||
else:
|
||||
self.filter += " OR "
|
||||
self.filter += a[1:]
|
||||
query = self._get_query()
|
||||
status, cursor = db.execute(self.conn, """
|
||||
SELECT count(quarantine.mail_id) AS total
|
||||
%s
|
||||
""" % query)
|
||||
if not status:
|
||||
print cursor
|
||||
self.count = 0
|
||||
else:
|
||||
self.count = int(cursor.fetchone()[0])
|
||||
|
||||
def _get_query(self):
|
||||
return """
|
||||
FROM quarantine, maddr, msgrcpt, msgs
|
||||
WHERE quarantine.mail_id=msgrcpt.mail_id
|
||||
AND msgrcpt.rid=maddr.id
|
||||
AND msgrcpt.mail_id=msgs.mail_id
|
||||
AND quarantine.chunk_ind=1
|
||||
%s
|
||||
ORDER BY msgs.time_num DESC
|
||||
""" % self.filter
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
return self.count
|
||||
|
||||
def fetch(self, start=None, stop=None, **kwargs):
|
||||
query = self._get_query()
|
||||
status, cursor = db.execute(self.conn, """
|
||||
SELECT msgs.from_addr, maddr.email, msgs.subject, msgs.content, quarantine.mail_id,
|
||||
msgs.time_num, msgs.content
|
||||
%s
|
||||
LIMIT %d,%d
|
||||
""" % (query, start - 1, stop - 1))
|
||||
if not status:
|
||||
print cursor
|
||||
return []
|
||||
emails = []
|
||||
rows = cursor.fetchall()
|
||||
for row in rows:
|
||||
emails.append({"from" : row[0], "to" : row[1],
|
||||
"subject" : row[2], "content" : row[3],
|
||||
"mailid" : row[4], "date" : row[5],
|
||||
"type" : row[6]})
|
||||
return emails
|
||||
|
||||
class SQLlisting(EmailListing):
|
||||
tpl = "amavis_quarantine/index.html"
|
||||
tbltype = Qtable
|
||||
deflocation = "listing/"
|
||||
defcallback = "updatelisting"
|
||||
|
||||
def __init__(self, filter, **kwargs):
|
||||
self.mbc = SQLconnector(filter)
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
@@ -1,49 +1,12 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "common/email_listing.html" %}
|
||||
{% load i18n %}
|
||||
{% load amextras %}
|
||||
{% block html_head %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<style type="text/css">
|
||||
form {
|
||||
margin: 0 auto;
|
||||
width: 98%;
|
||||
}
|
||||
|
||||
#quarmenu {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
#listing {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
#listheader {
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
float: right;
|
||||
}
|
||||
|
||||
#counter {
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: smaller;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
{% block css %}
|
||||
#searchbox {
|
||||
float: right;
|
||||
/*float: left;*/ /* Marche pas ici?! obligé de le faire en JS sous FF */
|
||||
margin-right: 10px;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#searchbox img {
|
||||
@@ -55,122 +18,137 @@ td {
|
||||
#searchparams {
|
||||
background: white;
|
||||
padding: 5px;
|
||||
width: 90%;
|
||||
border-bottom: 5px solid #CCCCCC;
|
||||
border-left: 5px solid #CCCCCC;
|
||||
border-right: 5px solid #CCCCCC;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEvent("domready", function() {
|
||||
thead tr {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
mysubmit = function(event, name) {
|
||||
event.stop();
|
||||
new Element('input', {
|
||||
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
|
||||
}).inject($("quarlistingform"), "top");
|
||||
$("quarlistingform").set("send", {
|
||||
}).inject($("listingform"), "top");
|
||||
{% url extensions.amavis_quarantine.main.process as actionurl %}
|
||||
$("listingform").set("action", "{{ actionurl }}");
|
||||
$("listingform").set("send", {
|
||||
onSuccess : function(res) {
|
||||
response = JSON.decode(res);
|
||||
location.href = response.url;
|
||||
actioncallback(response);
|
||||
}
|
||||
});
|
||||
$("quarlistingform").send();
|
||||
$("listingform").send();
|
||||
}
|
||||
|
||||
checkSelection = function(text) {
|
||||
var getout = $$("input[name=selection]").every(function(item, index) {
|
||||
return item.checked == false;
|
||||
updatehash = function(evt) {
|
||||
evt.stop();
|
||||
current_anchor.parse_string(evt.target.get("href"), true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
}
|
||||
|
||||
appendhash = function(evt) {
|
||||
evt.stop();
|
||||
location.hash += evt.target.get("href");
|
||||
}
|
||||
|
||||
updateparams = function(evt) {
|
||||
evt.stop();
|
||||
current_anchor.updateparams(evt.target.get("href"));
|
||||
current_anchor.update();
|
||||
}
|
||||
|
||||
register_callback("_listing", function(resp) {
|
||||
updatelisting(resp);
|
||||
$$("a[name=release]").addEvent("click", function(event) {
|
||||
if (!checkSelection("selection", "{% trans "Release this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "release");
|
||||
});
|
||||
if (getout) {
|
||||
return false;
|
||||
}
|
||||
return confirm(text);
|
||||
}
|
||||
|
||||
toggleSelection = function(value) {
|
||||
$$("input[name=selection]").each(function(obj) {
|
||||
obj.checked = value;
|
||||
$$("a[name=delete]").addEvent("click", function(event) {
|
||||
if (!checkSelection("selection", "{% trans "Delete this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "delete");
|
||||
});
|
||||
}
|
||||
|
||||
var myFx = new Fx.Slide($("searchparams"));
|
||||
myFx.hide();
|
||||
$("searchopts").addEvent("click", function(event) {
|
||||
myFx.cancel();
|
||||
myFx.toggle();
|
||||
});
|
||||
$$("input[name=scriteria]").addEvent("click", function(event) {
|
||||
myFx.cancel();
|
||||
myFx.toggle();
|
||||
});
|
||||
$("searchfield").addEvent("change", function(event) {
|
||||
var criteria = "";
|
||||
$$("input[name=scriteria]:checked").each(function(obj) {
|
||||
criteria = obj.value;
|
||||
});
|
||||
var url = "search"
|
||||
+ "?pattern=" + $("searchfield").value
|
||||
+ "&criteria=" + criteria;
|
||||
current_anchor.parse_string(url, true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
|
||||
$$("a[name=delete]").addEvent("click", function(event) {
|
||||
if (!checkSelection("{% trans "Delete this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "delete");
|
||||
});
|
||||
$$("a[name=release]").addEvent("click", function(event) {
|
||||
if (!checkSelection("{% trans "Release this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "release");
|
||||
});
|
||||
/*new Request.JSON({url: "{{ s_url }}", onSuccess: function(resp) {
|
||||
$("listing").set("html", resp.listing);
|
||||
$("navbar").set("html", resp.navbar);
|
||||
{% url extensions.amavis_quarantine.main.viewmail as view_url %}
|
||||
bindRows("{{ view_url }}", "{{ current_page }}");
|
||||
}}).get({"pattern" : $("searchfield").value, "criteria" : criteria});*/
|
||||
});
|
||||
$("searchfield").addEvent("click", function(event) {
|
||||
$("searchfield").select();
|
||||
});
|
||||
|
||||
if ($("selectall")) {
|
||||
$("selectall").addEvent("click", function(event) {
|
||||
if ($("selectall").checked) {
|
||||
toggleSelection(true);
|
||||
} else {
|
||||
toggleSelection(false);
|
||||
$("searchfield").addEvent("blur", function(event) {
|
||||
if ($("searchfield").value == "") {
|
||||
$("searchfield").set("value", "{% trans "Search..." %}");
|
||||
}
|
||||
});
|
||||
$("searchbox").setStyle("float", "left");
|
||||
$$("tr").addEvent("dblclick", viewmail);
|
||||
});
|
||||
|
||||
actioncallback = function(resp) {
|
||||
$("listing").set("html", "");
|
||||
$("menubar").set("html", "");
|
||||
$("navbar").set("html", "");
|
||||
if (resp.status == "ok") {
|
||||
infobox.show(resp.message, "green", 1);
|
||||
} else {
|
||||
infobox.show(resp.message, "red", 1);
|
||||
}
|
||||
current_anchor.parse_string(resp.url, true).setparams(navparams);
|
||||
current_anchor.update(1);
|
||||
}
|
||||
|
||||
{% url extensions.amavis_quarantine.main.search as s_url %}
|
||||
$("searchfield").addEvent("change", function(event) {
|
||||
var criteria = "";
|
||||
$$("input[name=scriteria]:checked").each(function(obj) {
|
||||
criteria = obj.value;
|
||||
});
|
||||
new Request.JSON({url: "{{ s_url }}", onSuccess: function(resp) {
|
||||
$("listing").set("html", resp.content);
|
||||
}}).get({"pattern" : $("searchfield").value, "criteria" : criteria});
|
||||
});
|
||||
actionclick = function(evt) {
|
||||
evt.stop();
|
||||
new Request.JSON({
|
||||
url: evt.target.get("href"),
|
||||
onSuccess: actioncallback
|
||||
}).get();
|
||||
}
|
||||
|
||||
$("searchfield").addEvent("click", function(event) {
|
||||
$("searchfield").select();
|
||||
register_callback("viewmail", function(resp) {
|
||||
updatelisting(resp);
|
||||
$$("a[name=back]").addEvent("click", updatehash);
|
||||
$$("a[name=delete]").addEvent("click", actionclick);
|
||||
$$("a[name=release]").addEvent("click", actionclick);
|
||||
$$("a[name=viewmode]").addEvent("click", updateparams);
|
||||
setDivHeight("mailcontent", 5, 0);
|
||||
});
|
||||
|
||||
$("searchfield").addEvent("blur", function(event) {
|
||||
if ($("searchfield").value == "") {
|
||||
$("searchfield").set("value", "{% trans "Search..." %}");
|
||||
}
|
||||
});
|
||||
|
||||
var myFx = new Fx.Slide($("searchparams"));
|
||||
myFx.hide();
|
||||
$("searchopts").addEvent("click", function(event) {
|
||||
myFx.cancel();
|
||||
myFx.toggle();
|
||||
});
|
||||
$$("input[name=scriteria]").addEvent("click", function(event) {
|
||||
myFx.cancel();
|
||||
myFx.toggle();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Quarantine" %}{% endblock %}
|
||||
{% block menu %}
|
||||
{% quar_menu "" perms %}
|
||||
<div id="searchbox">
|
||||
<img src="/static/pics/search.png" id="searchopts" />
|
||||
<input type="text" name="searchfield" id="searchfield" value="{% trans "Search..." %}" />
|
||||
<div id="searchparams">
|
||||
<input type="radio" name="scriteria" value="from_addr" checked>{% trans "From address" %}<br/>
|
||||
<input type="radio" name="scriteria" value="subject">{% trans "Subject" %}<br/>
|
||||
<input type="radio" name="scriteria" value="from_addr,subject">{% trans "Both" %}<br/>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="form-container">
|
||||
{% url extensions.amavis_quarantine.main.process as actionurl %}
|
||||
<form method="POST" action="{{ actionurl }}" id="quarlistingform">
|
||||
<input type="hidden" name="pagenum" id="pagenum" value="{{ current_page }}" />
|
||||
|
||||
<div id="listing">
|
||||
{{ content }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if not count %}
|
||||
<div id="notifybox">{% trans "No messages in quarantine" %}</div>
|
||||
{% else %}
|
||||
<div id="listheader">
|
||||
<div id="counter">
|
||||
Messages {{ emails.start_index }} à {{ emails.end_index }} sur {{ count }}
|
||||
</div>
|
||||
<div id="navbar">
|
||||
{% if emails.has_previous %}
|
||||
<a href="?page=1">
|
||||
<img src="/static/pics/first.png" border="0" title="{% trans "First" %}" />
|
||||
</a>
|
||||
<a href="?page={{ emails.previous_page_number }}">
|
||||
<img src="/static/pics/back.png" border="0" title="{% trans "Previous" %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if emails.has_next %}
|
||||
<a href="?page={{ emails.next_page_number }}">
|
||||
<img src="/static/pics/next.png" border="0" title="{% trans "Next" %}" />
|
||||
</a>
|
||||
<a href="?page={{ last_page }}">
|
||||
<img src="/static/pics/last.png" border="0" title="{% trans "Last" %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th><input type="checkbox" name="selectall" id="selectall" /></th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th width="150px">{% trans "From" %}</th>
|
||||
<th>{% trans "Subject" %}</th>
|
||||
<th width="100px">{% trans "Date" %}</th>
|
||||
</tr>
|
||||
{% for m in emails.object_list %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<td><input type="checkbox" name="selection" id="selection"
|
||||
value="{{ m.mailid }}" /></td>
|
||||
<td>{{ m.type }}</td>
|
||||
<td>{{ m.from }}</td>
|
||||
{% url extensions.amavis_quarantine.main.viewmail m.mailid as view_url %}
|
||||
<td><a href="{{ view_url }}?page={{ current_page }}">{{ m.subject }}</a></td>
|
||||
<td>{{ m.time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endif %}
|
||||
@@ -1,11 +1,11 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<style type="text/css">
|
||||
td {
|
||||
font-size: smaller;
|
||||
font-size: 1em;
|
||||
height: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
td.left {
|
||||
@@ -14,10 +14,23 @@ td.left {
|
||||
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEvent("domready", function() {
|
||||
var spage = $("page").getSize();
|
||||
var stable = $("table-container").getSize();
|
||||
var slogo = $("header").getSize();
|
||||
var iheight = spage.y - stable.y - slogo.y;
|
||||
|
||||
$("mailcontent").setStyle("height", iheight + "px");
|
||||
parse_menubar("menubar");
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Mail content" %}{% endblock %}
|
||||
{% block menu %}
|
||||
|
||||
@@ -7,25 +7,22 @@ from mailng.extensions import amavis_quarantine
|
||||
register = template.Library()
|
||||
|
||||
@register.simple_tag
|
||||
def viewm_menu(selection, mail_id, page_id, perms):
|
||||
def viewm_menu(selection, session, mail_id, perms):
|
||||
options_menu = [
|
||||
{"name" : "plain",
|
||||
{"name" : "viewmode",
|
||||
"label" : _("View as plain text"),
|
||||
"url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id])
|
||||
+ "?page=%s&mode=plain" % page_id},
|
||||
{"name" : "html",
|
||||
"url" : "?mode=plain&links=0"},
|
||||
{"name" : "viewmode",
|
||||
"label" : _("View as HTML"),
|
||||
"url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id])
|
||||
+ "?page=%s&mode=html" % page_id},
|
||||
{"name" : "links",
|
||||
"url" : "?mode=html&links=0"},
|
||||
{"name" : "viewmode",
|
||||
"label" : _("Activate links"),
|
||||
"url" : reverse(amavis_quarantine.main.viewmail, args=[mail_id])
|
||||
+ "?page=%s&mode=html&links=1" % page_id}
|
||||
"url" : "?mode=html&links=1"}
|
||||
]
|
||||
|
||||
entries = [
|
||||
{"name" : "back",
|
||||
"url" : reverse(amavis_quarantine.main.index) + "?page=%s" % page_id,
|
||||
"url" : "?page=%s" % session["page"],
|
||||
"img" : "/static/pics/back.png",
|
||||
"label" : _("Back to list")},
|
||||
{"name" : "headers",
|
||||
@@ -35,20 +32,19 @@ def viewm_menu(selection, mail_id, page_id, perms):
|
||||
"class" : "boxed",
|
||||
"rel" : "{handler:'iframe',size:{x:600,y:500}}"},
|
||||
{"name" : "release",
|
||||
"url" : reverse(amavis_quarantine.main.release, args=[mail_id])
|
||||
+ "?page=%s" % page_id,
|
||||
"url" : reverse(amavis_quarantine.main.release, args=[mail_id]),
|
||||
"img" : "/static/pics/release.png",
|
||||
"label" : _("Release"),
|
||||
"confirm" : _("Release this message?")},
|
||||
{"name" : "delete",
|
||||
"url" : reverse(amavis_quarantine.main.delete, args=[mail_id])
|
||||
+ "?page=%s" % page_id,
|
||||
"url" : reverse(amavis_quarantine.main.delete, args=[mail_id]),
|
||||
"img" : "/static/pics/remove.png",
|
||||
"label" : _("Delete"),
|
||||
"confirm" : _("Delete this message?")},
|
||||
{"name" : "options",
|
||||
"label" : _("Options"),
|
||||
"img" : "/static/pics/settings.png",
|
||||
"class" : "menubardropdown",
|
||||
"menu" : options_menu}
|
||||
]
|
||||
|
||||
@@ -69,6 +65,8 @@ def quar_menu(selection, perms):
|
||||
"label" : _("Delete")},
|
||||
]
|
||||
|
||||
return render_to_string('main/menu.html',
|
||||
menu = render_to_string('main/menu.html',
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
searchbar = render_to_string('common/email_searchbar.html', {})
|
||||
return menu + searchbar
|
||||
|
||||
@@ -2,11 +2,12 @@ from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.amavis_quarantine.main',
|
||||
(r'^$', 'index'),
|
||||
(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
(r'^listing/$', '_listing'),
|
||||
(r'^getmailcontent/(?P<mail_id>[\w\-\+]+)/$', 'getmailcontent'),
|
||||
(r'^viewheaders/(?P<mail_id>[\w\-\+]+)/$', 'viewheaders'),
|
||||
(r'^(?P<mail_id>[\w\-\+]+)/headers/$', 'viewheaders'),
|
||||
(r'^process/$', 'process'),
|
||||
(r'^delete/(?P<mail_id>[\w\-\+]+)/$', 'delete'),
|
||||
(r'^release/(?P<mail_id>[\w\-\+]+)/$', 'release'),
|
||||
(r'^search/', 'search'),
|
||||
(r'^(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
)
|
||||
|
||||
@@ -49,6 +49,10 @@ size_avg_template = {
|
||||
}
|
||||
}
|
||||
|
||||
tpl = {'traffic':traffic_avg_template,
|
||||
'badtraffic':badtraffic_avg_template,
|
||||
'size':size_avg_template}
|
||||
|
||||
class Grapher(object):
|
||||
def __init__(self):
|
||||
self.rrd_rootdir = getoption("RRD_ROOTDIR", "/tmp")
|
||||
@@ -57,9 +61,10 @@ class Grapher(object):
|
||||
def process(self, target, suffix, start, end, tpl=traffic_avg_template):
|
||||
rrdfile = "%s/%s.rrd" % (self.rrd_rootdir, target)
|
||||
if not os.path.exists(rrdfile):
|
||||
print "[graph] no rrd file %s detected" %rrdfile
|
||||
return
|
||||
ext = "png"
|
||||
path = "%s/%s_%s_%s_%s.%s" % (self.img_rootdir, tpl['name'],
|
||||
path = "%s/%s_%s_%s_%s.%s" % (self.img_rootdir, tpl['name'],
|
||||
target, tpl['cf'], suffix, ext)
|
||||
start = str(start)
|
||||
end = str(end)
|
||||
@@ -69,10 +74,10 @@ class Grapher(object):
|
||||
defs += ['DEF:%s=%s:%s:%s' % (v, rrdfile, v, tpl['cf']),
|
||||
'CDEF:%spm=%s,60,*' % (v, v)]
|
||||
type = d.has_key("type") and d["type"] or "LINE"
|
||||
lines += ["%s:%spm%s:%s" % (type, v, d["color"],
|
||||
lines += ["%s:%spm%s:%s" % (type, v, d["color"],
|
||||
d["legend"].encode("utf-8"))]
|
||||
params = defs + lines
|
||||
rrdtool.graph(path,
|
||||
rrdtool.graph(str(path),
|
||||
"--imgformat", "PNG",
|
||||
"--width", tpl["width"],
|
||||
"--height", tpl["height"],
|
||||
@@ -84,6 +89,11 @@ class Grapher(object):
|
||||
"--units-exponent", "0",
|
||||
*params)
|
||||
|
||||
if not os.path.exists(path):
|
||||
print "[graph] Impossible to create %s graph" %path
|
||||
else:
|
||||
print "[graph] %s graph created" %path
|
||||
|
||||
def make_defaults(self, target, tpl=traffic_avg_template):
|
||||
end = "%d" % int(time.mktime(time.localtime()))
|
||||
self.process(target, "day", "-1day", end, tpl)
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
import os
|
||||
import re
|
||||
import rrdtool
|
||||
import string
|
||||
import string, pdb
|
||||
from optparse import OptionParser
|
||||
from mailng.lib import getoption
|
||||
from mailng.admin.models import Domain
|
||||
@@ -35,6 +35,25 @@ points_per_sample = 3
|
||||
variables = ["sent", "recv", "bounced", "reject", "spam", "virus",
|
||||
"size_sent", "size_recv"]
|
||||
|
||||
def str2Time(y, M, d, h ="0", m ="0", s="0"):
|
||||
"""str2Time
|
||||
|
||||
return epoch time from Year Month Day Hour:Minute:Second time format
|
||||
"""
|
||||
try:
|
||||
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
|
||||
"%Y %b %d %H:%M:%S")
|
||||
except:
|
||||
# try with 01-12 month format
|
||||
try:
|
||||
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
|
||||
"%Y %m %d %H:%M:%S")
|
||||
except:
|
||||
print "[rrd] ERROR unrecognized %s time format" %(y, M, d, h, m, s)
|
||||
return 0
|
||||
return int(time.mktime(local))
|
||||
|
||||
|
||||
class LogParser(object):
|
||||
def __init__(self, logfile, workdir,
|
||||
year=None, debug=False, verbose=False):
|
||||
@@ -42,17 +61,20 @@ class LogParser(object):
|
||||
try:
|
||||
self.f = open(logfile)
|
||||
except IOError, errno:
|
||||
print "%s" % errno
|
||||
if self.debug:
|
||||
print "%s" % errno
|
||||
sys.exit(1)
|
||||
self.workdir = workdir
|
||||
self.year = year
|
||||
self.debug = debug
|
||||
self.verbose = verbose
|
||||
self.cfs = ['AVERAGE', 'MAX']
|
||||
|
||||
|
||||
self.last_month = None
|
||||
if not self.year:
|
||||
self.year = time.localtime().tm_year
|
||||
if self.debug:
|
||||
print "[rrd] Dealing with year %s" %self.year
|
||||
self.data = {}
|
||||
domains = Domain.objects.all()
|
||||
self.domains = []
|
||||
@@ -65,19 +87,6 @@ class LogParser(object):
|
||||
self.lupdates = {}
|
||||
self.line_expr = re.compile("(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\w+)\s+(\w+)/?\w*[[](\d+)[]]:\s+(.*)")
|
||||
|
||||
def str2Time(self, y, M, d, h, m, s):
|
||||
"""str2Time
|
||||
|
||||
return epoch time from Year Month Day Hour:Minute:Second time format
|
||||
"""
|
||||
try:
|
||||
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
|
||||
"%Y %b %d %H:%M:%S")
|
||||
except:
|
||||
print "[rrd] ERROR unrecognized %s time format" %(y, M, d, h, m, s)
|
||||
return 0
|
||||
return int(time.mktime(local))
|
||||
|
||||
def init_rrd(self, fname, m):
|
||||
"""init_rrd
|
||||
|
||||
@@ -128,7 +137,8 @@ class LogParser(object):
|
||||
m = t - (t % rrdstep)
|
||||
if not os.path.exists(fname):
|
||||
self.lupdates[fname] = self.init_rrd(fname, m)
|
||||
print "[rrd] create new RRD file"
|
||||
if self.debug:
|
||||
print "[rrd] create new RRD file %s" %fname
|
||||
else:
|
||||
if not self.lupdates.has_key(fname):
|
||||
self.lupdates[fname] = rrdtool.last(fname)
|
||||
@@ -186,60 +196,76 @@ class LogParser(object):
|
||||
self.data["global"][cur_t][counter] += val
|
||||
|
||||
def process(self):
|
||||
id_expr = re.compile("(\w{9,18}): (.*)")
|
||||
prev_se = -1
|
||||
prev_mi = -1
|
||||
prev_ho = -1
|
||||
for line in self.f.readlines():
|
||||
m = self.line_expr.match(line)
|
||||
if not m:
|
||||
continue
|
||||
(mo, da, ho, mi, se, host, prog, pid, log) = m.groups()
|
||||
|
||||
se = int(int(se) / rrdstep) # rrd step is one-minute => se = 0
|
||||
cur_t = self.str2Time(self.year, mo, da, ho, mi, se)
|
||||
cur_t = cur_t - cur_t % rrdstep
|
||||
|
||||
m = re.search("(\w{10}): message-id=<([^>]*)>", log)
|
||||
if prev_se != se or prev_mi != mi or prev_ho != ho:
|
||||
cur_t = str2Time(self.year, mo, da, ho, mi, se)
|
||||
cur_t = cur_t - cur_t % rrdstep
|
||||
prev_mi = mi
|
||||
prev_ho = ho
|
||||
prev_se = se
|
||||
m = id_expr.search(log)
|
||||
if m:
|
||||
self.workdict[m.group(1)] = {'from' : m.group(2), 'size' : 0}
|
||||
continue
|
||||
(line_id, line_log) = m.groups()
|
||||
|
||||
m = re.search("(\w{10}): from=<([^>]*)>, size=(\d+)", log)
|
||||
if m:
|
||||
self.workdict[m.group(1)] = {'from' : m.group(2),
|
||||
'size' : string.atoi(m.group(3))}
|
||||
continue
|
||||
|
||||
m = re.search("(\w{10}): to=<([^>]*)>.*status=(\S+)", log)
|
||||
if m:
|
||||
if not self.workdict.has_key(m.group(1)):
|
||||
print "Inconsistent mail (%s: %s), skipping" % (m.group(1), m.group(2))
|
||||
m = re.search("message-id=<([^>]*)>", line_log)
|
||||
if m:
|
||||
self.workdict[line_id] = {'from' : m.group(1), 'size' : 0}
|
||||
continue
|
||||
if not m.group(3) in variables:
|
||||
print "Unsupported status %s, skipping" % m.group(3)
|
||||
|
||||
m = re.search("from=<([^>]*)>, size=(\d+)", line_log)
|
||||
if m:
|
||||
self.workdict[line_id] = {'from' : m.group(1),
|
||||
'size' : string.atoi(m.group(2))}
|
||||
continue
|
||||
addrfrom = re.match("([^@]+)@(.+)", self.workdict[m.group(1)]['from'])
|
||||
if addrfrom and addrfrom.group(2) in self.domains:
|
||||
self.inc_counter(addrfrom.group(2), cur_t, 'sent')
|
||||
self.inc_counter(addrfrom.group(2), cur_t, 'size_sent',
|
||||
self.workdict[m.group(1)]['size'])
|
||||
addrto = re.match("([^@]+)@(.+)", m.group(2))
|
||||
if addrto.group(2) in self.domains:
|
||||
if m.group(3) == "sent":
|
||||
self.inc_counter(addrto.group(2), cur_t, 'recv')
|
||||
self.inc_counter(addrto.group(2), cur_t, 'size_recv',
|
||||
self.workdict[m.group(1)]['size'])
|
||||
else:
|
||||
self.inc_counter(addrto.group(2), cur_t, m.group(3))
|
||||
continue
|
||||
|
||||
m = re.search("NOQUEUE: reject: .*from=<(.*)> to=<([^>]*)>", log)
|
||||
if m:
|
||||
addrto = re.match("([^@]+)@(.+)", m.group(2))
|
||||
if addrto and addrto.group(2) in self.domains:
|
||||
self.inc_counter(addrto.group(2), cur_t, 'reject')
|
||||
continue
|
||||
|
||||
|
||||
m = re.search("to=<([^>]*)>.*status=(\S+)", line_log)
|
||||
if m:
|
||||
if not self.workdict.has_key(line_id):
|
||||
if self.debug:
|
||||
print "Inconsistent mail (%s: %s), skipping" % (line_id, m.group(1))
|
||||
continue
|
||||
if not m.group(2) in variables:
|
||||
if self.debug:
|
||||
print "Unsupported status %s, skipping" % m.group(2)
|
||||
continue
|
||||
|
||||
addrfrom = re.match("([^@]+)@(.+)", self.workdict[line_id]['from'])
|
||||
if addrfrom and addrfrom.group(2) in self.domains:
|
||||
self.inc_counter(addrfrom.group(2), cur_t, 'sent')
|
||||
self.inc_counter(addrfrom.group(2), cur_t, 'size_sent',
|
||||
self.workdict[line_id]['size'])
|
||||
addrto = re.match("([^@]+)@(.+)", m.group(1))
|
||||
if addrto.group(2) in self.domains:
|
||||
if m.group(2) == "sent":
|
||||
self.inc_counter(addrto.group(2), cur_t, 'recv')
|
||||
self.inc_counter(addrto.group(2), cur_t, 'size_recv',
|
||||
self.workdict[line_id]['size'])
|
||||
else:
|
||||
self.inc_counter(addrto.group(2), cur_t, m.group(2))
|
||||
continue
|
||||
else:
|
||||
m = re.search("NOQUEUE: reject: .*from=<(.*)> to=<([^>]*)>", log)
|
||||
if m:
|
||||
addrto = re.match("([^@]+)@(.+)", m.group(2))
|
||||
if addrto and addrto.group(2) in self.domains:
|
||||
self.inc_counter(addrto.group(2), cur_t, 'reject')
|
||||
continue
|
||||
|
||||
# Sort everything by time
|
||||
G = grapher.Grapher()
|
||||
for dom, data in self.data.iteritems():
|
||||
if self.debug:
|
||||
print "[rrd] dealing with domain %s" %dom
|
||||
sortedData = {}
|
||||
sortedData = [ (i, data[i]) for i in sorted(data.keys()) ]
|
||||
for t, dict in sortedData:
|
||||
@@ -256,12 +282,12 @@ if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
parser.add_option("-l","--logFile", default=log_file,
|
||||
help="postfix log in syslog format", metavar="FILE")
|
||||
parser.add_option("-v","--verbose", default=False, action="store_true",
|
||||
parser.add_option("-v","--verbose", default=False, action="store_true",
|
||||
dest="verbose", help="set verbose mode")
|
||||
parser.add_option("-d","--debug", default=False, action="store_true",
|
||||
parser.add_option("-d","--debug", default=False, action="store_true",
|
||||
dest="debug", help="set debug mode")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
|
||||
P = LogParser(options.logFile, rrd_rootdir,
|
||||
debug=options.debug, verbose=options.verbose)
|
||||
P.process()
|
||||
|
||||
@@ -7,19 +7,33 @@ This module provides rrdtool support to retreive statistics
|
||||
from postfix log : sent, received, bounced, rejected
|
||||
|
||||
"""
|
||||
import calendar
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf.urls.defaults import *
|
||||
from mailng.lib import events, _render
|
||||
from mailng.lib import events, parameters, _render
|
||||
from mailng.admin.views import good_domain
|
||||
from mailng.admin.models import Domain, Mailbox
|
||||
from django.contrib.auth.decorators \
|
||||
import login_required, user_passes_test, permission_required
|
||||
from extensions.stats.grapher import Grapher, tpl
|
||||
from extensions.stats.logparser import str2Time
|
||||
from mailng.lib import getoption
|
||||
|
||||
graph_types = ['AVERAGE', 'MAX']
|
||||
|
||||
graph_list = [{"name" : "traffic", "label" : _("Average normal traffic")},
|
||||
{"name" : "badtraffic", "label" : _("Average bad traffic")},
|
||||
{"name" : "size", "label" : _("Average normal traffic size")}]
|
||||
|
||||
def init():
|
||||
events.register("AdminMenuDisplay", menu)
|
||||
parameters.register("stats", "LOGFILE", "string", "/var/log/mail.log",
|
||||
help=_("Path to log file used to collect statistics"))
|
||||
parameters.register("stats", "RRD_ROOTDIR", "string", "/tmp/mailng",
|
||||
help=_("Path to directory where RRD files are stored"))
|
||||
parameters.register("stats", "IMG_ROOTDIR", "string", "/tmp/mailng",
|
||||
help=_("Path to directory where PNG files are stored"))
|
||||
|
||||
def urls():
|
||||
return (r'^mailng/stats/',
|
||||
@@ -77,26 +91,87 @@ def graph_display(request,dom_id,graph_t=graph_types):
|
||||
|
||||
return _render(request, 'stats/index.html', {
|
||||
"page" : "Domain statistics", "graph" : graph_nature,
|
||||
"domain":domain,"domains" : domains, "messages" : errors ,
|
||||
"types" : graph_type, "tmp_path" : tmp_path})
|
||||
"domain": domain, "domains" : domains, "messages" : errors,
|
||||
"types" : graph_type, "tmp_path" : tmp_path,
|
||||
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def adminindex(request):
|
||||
CH = calendar.HTMLCalendar(calendar.MONDAY)
|
||||
CH_print = CH.formatmonth(2009,11)
|
||||
period = request.GET.has_key("period") and request.GET["period"] or "day"
|
||||
domain = request.GET.has_key("domain") and request.GET["domain"] or "global"
|
||||
if domain != "global":
|
||||
domain = Domain.objects.get(pk=domain)
|
||||
domain = domain.name
|
||||
domains = Domain.objects.all()
|
||||
G = Grapher()
|
||||
for name, t in tpl.iteritems():
|
||||
G.make_defaults(domain, t)
|
||||
|
||||
return _render(request, 'stats/adminindex.html', {
|
||||
"domains" : domains, "domain" : domain,
|
||||
"graphs" : graph_list,
|
||||
"periods" : ["day", "week", "month", "year","Custom"],
|
||||
"period" : period, "cal" : CH_print,
|
||||
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def custom_period(request):
|
||||
start = None
|
||||
end = None
|
||||
start_ref = None
|
||||
end_ref = None
|
||||
domains = Domain.objects.all()
|
||||
domain = request.GET.has_key("domain") and request.GET["domain"] or "global"
|
||||
period = request.GET.has_key("period") and request.GET["period"] or "Custom"
|
||||
|
||||
if domain != "global":
|
||||
domain = Domain.objects.get(pk=domain)
|
||||
domain = domain.name
|
||||
try:
|
||||
start_ref = str(request.POST.get('start'))
|
||||
start = start_ref.split(', ')[1]
|
||||
end_ref = str(request.POST.get('end'))
|
||||
end = end_ref.split(', ')[1]
|
||||
except:
|
||||
pass
|
||||
|
||||
if not end or not start:
|
||||
return _render(request, 'stats/adminindex.html', {
|
||||
"domains" : domains, "domain" : domain,
|
||||
"graphs" : [{"name" : "traffic", "label" : _("Average normal traffic")},
|
||||
{"name" : "badtraffic", "label" : _("Average bad traffic")},
|
||||
{"name" : "size", "label" : _("Average normal traffic size")}],
|
||||
"periods" : ["day", "week", "month", "year"],
|
||||
"period" : period
|
||||
})
|
||||
"graphs" : graph_list,
|
||||
"period" : period,
|
||||
"periods" : ["day", "week", "month", "year","Custom"],
|
||||
"period_name":None,
|
||||
"start" : start_ref,
|
||||
"end" : end_ref,
|
||||
"messages" : ["Custom period not selected"],
|
||||
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
|
||||
|
||||
period_name = "%s_%s" %(start.replace('/',''),end.replace('/',''))
|
||||
G = Grapher()
|
||||
for tpl_name in graph_list:
|
||||
G.process(domain,
|
||||
period_name,
|
||||
str2Time(*start.split('/')),
|
||||
str2Time(*end.split('/')),
|
||||
tpl[tpl_name['name']])
|
||||
|
||||
print "[stats] ", period
|
||||
return _render(request, 'stats/adminindex.html', {
|
||||
"domains" : domains, "domain" : domain,
|
||||
"graphs" : graph_list,
|
||||
"period" : period,
|
||||
"periods" : ["day", "week", "month", "year","Custom"],
|
||||
"period_name": period_name,
|
||||
"start" : start_ref,
|
||||
"end" : end_ref,
|
||||
"messages" : None,
|
||||
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
|
||||
|
||||
|
||||
@login_required
|
||||
@good_domain
|
||||
@@ -108,10 +183,8 @@ def index(request, dom_id=None):
|
||||
domain = Domain.objects.get(pk=dom_id)
|
||||
|
||||
return _render(request, 'stats/index.html', {
|
||||
"domain" : domain,
|
||||
"graphs" : [{"name" : "traffic", "label" : _("Average normal traffic")},
|
||||
{"name" : "badtraffic", "label" : _("Average bad traffic")},
|
||||
{"name" : "size", "label" : _("Average normal traffic size")}],
|
||||
"periods" : ["day", "week", "month", "year"],
|
||||
"period" : period
|
||||
})
|
||||
"domain" : domain,
|
||||
"graphs" : graph_list,
|
||||
"periods" : ["day", "week", "month", "year","Custom"],
|
||||
"period" : period,
|
||||
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% load stats_extra %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
|
||||
<style type="text/css">
|
||||
#graphcontent {
|
||||
margin: 25px auto 10px;
|
||||
@@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
#selectors {
|
||||
float: right;
|
||||
float: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,6 +27,7 @@ window.addEvent("domready", function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="/static/js/calendar.js"></script>
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Global statistics" %}{% endblock %}
|
||||
{% block content %}
|
||||
@@ -46,7 +47,15 @@ window.addEvent("domready", function() {
|
||||
<option value="{{ p }}" {% ifequal p period %}selected{% endifequal %}>{% getLabel p %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% ifequal period "Custom" %}
|
||||
{% include "stats/calendar.html" %}
|
||||
{% endifequal %}
|
||||
</div>
|
||||
{% if start or end %}
|
||||
<h3>{% trans "Custom period : from" %} {{ start }} {% trans "to" %} {{ end }}</h3>
|
||||
{% endif %}
|
||||
<div style="float:None;">
|
||||
{% include "stats/graphs.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{% load i18n %}
|
||||
<div style="border:1px solid #dddddd;float:right;padding:3px;margin-top:-3px;">
|
||||
{% url extensions.stats.main.custom_period as c_stats_url %}
|
||||
<form class="calendarForm" action="{{ c_stats_url }}" method="POST">
|
||||
{% trans "Start" %}: <input id="dateTravelStart" name="start" value="{{ start }}" type="text"/>
|
||||
{% trans "End" %}: <input id="dateTravelEnd" name="end" value="{{ end }}" type="text" />
|
||||
<input TYPE=SUBMIT VALUE="ok">
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
myCal = new Calendar(
|
||||
{
|
||||
dateTravelStart: 'D, Y/m/d',
|
||||
dateTravelEnd : 'D, Y/m/d'
|
||||
},
|
||||
{
|
||||
direction: 0, // -1 past, 0 past + future, 1 future
|
||||
pad:1,
|
||||
offset: 1, // first day of the week: 0 = sunday, 1 = monday, etc..
|
||||
tweak: {x: -147, y: 18} // tweak calendar positioning
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--
|
||||
<form name="calendar">
|
||||
<textarea type="text" rows="0" cols="5">{{ sdate }}</textarea>
|
||||
<textarea type="text" rows="0" cols="5">{{ edate }}</textarea>
|
||||
</select>
|
||||
-->
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
{% for elems in graphs %}
|
||||
<h3>{{ elems.label }}</h3>
|
||||
<p><img src="/static/graphs/{{ elems.name }}_{{ domain }}_AVERAGE_{{ period }}.png"
|
||||
<p><img src="/img/stats/{{ elems.name }}_{{ domain }}_AVERAGE_{% ifequal period 'Custom' %}{{ period_name }}{% else %}{{ period }}{% endifequal %}.png"
|
||||
alt="{% trans 'No statistics available' %}" /></p>
|
||||
{% endfor %}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
|
||||
<style type="text/css">
|
||||
#selectors {
|
||||
float: right;
|
||||
float: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -17,6 +17,7 @@ window.addEvent("domready", function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script type="text/javascript" src="/static/js/calendar.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
@@ -33,7 +34,15 @@ window.addEvent("domready", function() {
|
||||
<option value="{{ p }}" {% ifequal p period %}selected{% endifequal %}>{{ p }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% ifequal period "Custom" %}
|
||||
{% include "stats/calendar.html" %}
|
||||
{% endifequal %}
|
||||
</div>
|
||||
{% if start or end %}
|
||||
<h3>{% trans "Custom period : from" %} {{ start }} {% trans "to" %} {{ end }}</h3>
|
||||
{% endif %}
|
||||
<div style="float:None;">
|
||||
{% include "stats/graphs.html" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.stats.main',
|
||||
url(r'^$', 'adminindex', name='fullindex'),
|
||||
url(r'^custom_period$', 'custom_period'),
|
||||
url(r'^(?P<dom_id>\d+)/$', 'index', name='domindex'),
|
||||
(r'^display/(?P<dom_id>\d+)/(?P<graph_t>\w+)/$', 'graph_display'),
|
||||
(r'^domain/(?P<dom_id>\d+)/$', 'domain'),
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
class ComposeMailForm(forms.Form):
|
||||
from_ = forms.CharField(label=_("From"))
|
||||
to = forms.CharField(label=_("To"), widget=forms.Textarea())
|
||||
cc = forms.CharField(label=_("Cc"), widget=forms.Textarea(), required=False)
|
||||
subject = forms.CharField(label=_("Subject"), max_length=255)
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import time
|
||||
import imaplib
|
||||
import ssl
|
||||
from datetime import datetime, timedelta
|
||||
from email.header import decode_header
|
||||
import email.utils
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import decode, tables, imap_utf7, Singleton
|
||||
from mailng.lib.email_listing import MBconnector, EmailListing, Email
|
||||
from mailng.lib import tables, imap_utf7, parameters
|
||||
|
||||
|
||||
class WMtable(tables.Table):
|
||||
tableid = "emails"
|
||||
idkey = "imapid"
|
||||
# selection = tables.SelectionColumn("selection", width="20px", first=True)
|
||||
_1_flags = tables.ImgColumn("flags", width="4%",
|
||||
header="<input type='checkbox' name='toggleselect' id='toggleselect' />")
|
||||
_2_subject = tables.Column("subject", label=_("Subject"),
|
||||
cssclass="draggable", width="50%")
|
||||
_3_from_ = tables.Column("from", width="20%", label=_("From"))
|
||||
_4_date = tables.Column("date", width="20%", label=_("Date"))
|
||||
|
||||
|
||||
def parse(self, header, value):
|
||||
try:
|
||||
return getattr(IMAPheader, "parse_%s" % header)(value)
|
||||
except AttributeError:
|
||||
return value
|
||||
|
||||
class EmailAddress(object):
|
||||
addr_exp = re.compile("([^<\(]+)[<(]([^>)]+)[>)]")
|
||||
name_exp = re.compile('"([^"]+)"')
|
||||
|
||||
def __init__(self, address):
|
||||
self.fulladdress = address
|
||||
self.name = self.address = None
|
||||
m = EmailAddress.addr_exp.match(self.fulladdress)
|
||||
if m is None:
|
||||
return
|
||||
name = m.group(1).strip()
|
||||
self.address = m.group(2).strip()
|
||||
m2 = EmailAddress.name_exp.match(name)
|
||||
if m2:
|
||||
name = m2.group(1)
|
||||
self.name = ""
|
||||
for part in decode_header(name):
|
||||
if self.name != "":
|
||||
self.name += " "
|
||||
if part[1] is None:
|
||||
self.name += part[0]
|
||||
else:
|
||||
self.name += unicode(part[0], part[1])
|
||||
self.fulladdress = "%s <%s>" % (self.name, self.address)
|
||||
|
||||
def __str__(self):
|
||||
return self.fulladdress
|
||||
|
||||
class IMAPheader(object):
|
||||
@staticmethod
|
||||
def parse_address(value, **kwargs):
|
||||
addr = EmailAddress(value)
|
||||
if "full" in kwargs.keys() and kwargs["full"]:
|
||||
return addr.fulladdress
|
||||
return addr.name and addr.name or value
|
||||
|
||||
@staticmethod
|
||||
def parse_address_list(values, **kwargs):
|
||||
lst = values.split(",")
|
||||
result = ""
|
||||
for addr in lst:
|
||||
if result != "":
|
||||
result += ", "
|
||||
result += IMAPheader.parse_address(addr, **kwargs)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def parse_from(value, **kwargs):
|
||||
return IMAPheader.parse_address(value, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def parse_to(value, **kwargs):
|
||||
return IMAPheader.parse_address_list(value, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def parse_cc(value, **kwargs):
|
||||
return IMAPheader.parse_address_list(value, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def parse_reply_to(value, **kwargs):
|
||||
return IMAPheader.parse_address_list(value, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def parse_date(value, **kwargs):
|
||||
tmp = email.utils.parsedate_tz(value)
|
||||
if not tmp:
|
||||
return value
|
||||
ndate = datetime(*(tmp)[:7])
|
||||
now = datetime.now()
|
||||
if now - ndate > timedelta(7):
|
||||
return ndate.strftime("%d.%m.%Y %H:%M")
|
||||
return ndate.strftime("%a %H:%M")
|
||||
|
||||
@staticmethod
|
||||
def parse_subject(value, **kwargs):
|
||||
res = ""
|
||||
dcd = decode_header(value)
|
||||
for part in dcd:
|
||||
if res != "":
|
||||
res += " "
|
||||
res += part[0].strip(" ")
|
||||
return decode(res)
|
||||
|
||||
class IMAPconnector(object):
|
||||
__metaclass__ = Singleton
|
||||
|
||||
def __init__(self, request=None, user=None, password=None):
|
||||
self.address = parameters.get("webmail", "IMAP_SERVER")
|
||||
self.port = int(parameters.get("webmail", "IMAP_PORT"))
|
||||
if request:
|
||||
status, msg = self.login(request.user.username,
|
||||
request.session["password"])
|
||||
else:
|
||||
status, msg = self.login(user, password)
|
||||
if not status:
|
||||
raise Exception(msg)
|
||||
|
||||
def login(self, user, passwd):
|
||||
import imaplib
|
||||
try:
|
||||
secured = parameters.get("webmail", "IMAP_SECURED")
|
||||
if secured == "yes":
|
||||
self.m = imaplib.IMAP4_SSL(self.address, self.port)
|
||||
else:
|
||||
self.m = imaplib.IMAP4(self.address, self.port)
|
||||
except (imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Connection to IMAP server failed, check your configuration")
|
||||
try:
|
||||
self.m.login(user, passwd)
|
||||
except (imaplib.IMAP4.error, ssl.SSLError), error:
|
||||
return False, _("Authentication failed, check your configuration")
|
||||
|
||||
return True, None
|
||||
|
||||
def _encodefolder(self, folder):
|
||||
if not folder:
|
||||
return "INBOX"
|
||||
return folder.encode("imap4-utf-7")
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
"""An enhanced version of messages_count
|
||||
|
||||
With IMAP, to know how many messages a mailbox contains, we
|
||||
have to make a request to the server. To avoid requests
|
||||
multiplications, we sort messages in the same time. This will
|
||||
be usefull for other methods.
|
||||
|
||||
:param order: sorting order
|
||||
:param folder: mailbox to scan
|
||||
"""
|
||||
if "order" in kwargs.keys() and kwargs["order"]:
|
||||
sign = kwargs["order"][:1]
|
||||
criterion = kwargs["order"][1:].upper()
|
||||
if sign == '-':
|
||||
criterion = "REVERSE %s" % criterion
|
||||
else:
|
||||
criterion = "REVERSE DATE"
|
||||
print criterion
|
||||
|
||||
folder = kwargs.has_key("folder") and kwargs["folder"] or None
|
||||
(status, data) = self.m.select(self._encodefolder(folder))
|
||||
(status, data) = self.m.sort("(%s)" % criterion, "UTF-8", "(NOT DELETED)")
|
||||
self.messages = data[0].split()
|
||||
return len(self.messages)
|
||||
|
||||
def listfolders(self, topfolder='INBOX', md_folders=[]):
|
||||
(status, data) = self.m.list()
|
||||
result = []
|
||||
for mb in data:
|
||||
attrs, truc, name = mb.split()
|
||||
name = name.strip('"').decode("imap4-utf-7")
|
||||
present = False
|
||||
for mdf in md_folders:
|
||||
if mdf["name"] == name:
|
||||
present = True
|
||||
break
|
||||
if not present:
|
||||
result += [name]
|
||||
return sorted(result)
|
||||
|
||||
def msgseen(self, imapid):
|
||||
folder, id = imapid.split("/")
|
||||
self.m.store(id, "+FLAGS", "\\Seen")
|
||||
|
||||
def msgforwarded(self, folder, imapid):
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
self.m.store(imapid, "+FLAGS", "$Forwarded")
|
||||
|
||||
def move(self, msgset, oldfolder, newfolder):
|
||||
self.m.select(self._encodefolder(oldfolder), True)
|
||||
self.m.copy(msgset, newfolder)
|
||||
self.m.store(msgset, "+FLAGS", "\\Deleted")
|
||||
|
||||
def empty(self, folder):
|
||||
self.m.select(self._encodefolder(folder))
|
||||
typ, data = self.m.search(None, 'ALL')
|
||||
for num in data[0].split():
|
||||
self.m.store(num, "+FLAGS", "\\Deleted")
|
||||
self.m.expunge()
|
||||
|
||||
def fetch(self, start=None, stop=None, folder=None, all=False):
|
||||
if not start and not stop:
|
||||
return []
|
||||
result = []
|
||||
self.m.select(self._encodefolder(folder), True)
|
||||
if start and stop:
|
||||
submessages = self.messages[start - 1:stop]
|
||||
range = ",".join(submessages)
|
||||
else:
|
||||
submessages = [start]
|
||||
range = start
|
||||
if not all:
|
||||
query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])'
|
||||
else:
|
||||
query = '(RFC822)'
|
||||
typ, data = self.m.fetch(range, query)
|
||||
if not folder:
|
||||
folder = "INBOX"
|
||||
tmpdict = {}
|
||||
for response_part in data:
|
||||
if isinstance(response_part, tuple):
|
||||
imapid = response_part[0].split()[0]
|
||||
flags = imaplib.ParseFlags(response_part[0])
|
||||
msg = email.message_from_string(response_part[1])
|
||||
msg["imapid"] = "%s/%s" % (folder, imapid)
|
||||
if not "\\Seen" in flags:
|
||||
msg["class"] = "unseen"
|
||||
if "\\Answered" in flags:
|
||||
msg["img_flags"] = "/static/pics/answered.png"
|
||||
tmpdict[imapid] = msg
|
||||
for id in submessages:
|
||||
result += [tmpdict[id]]
|
||||
return result
|
||||
|
||||
class ImapListing(EmailListing):
|
||||
tpl = "webmail/index.html"
|
||||
tbltype = WMtable
|
||||
deflocation = "INBOX/"
|
||||
defcallback = "wm_updatelisting"
|
||||
|
||||
def __init__(self, user, password, **kwargs):
|
||||
self.mbc = IMAPconnector(user=user, password=password)
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
|
||||
def getfolders(self):
|
||||
md_folders = [{"name" : "INBOX", "icon" : "overview.png"},
|
||||
{"name" : 'Drafts'},
|
||||
{"name" : 'Junk'},
|
||||
{"name" : 'Sent'},
|
||||
{"name" : 'Trash', "icon" : "trash.png"}]
|
||||
folders = self.mbc.listfolders(md_folders=md_folders)
|
||||
for fd in folders:
|
||||
md_folders += [{"name" : fd}]
|
||||
return md_folders
|
||||
|
||||
class ImapEmail(Email):
|
||||
def __init__(self, msg, addrfull=False, **kwargs):
|
||||
Email.__init__(self, msg, **kwargs)
|
||||
|
||||
fields = ["Subject", "From", "To", "Reply-To", "Cc", "Date"]
|
||||
for f in fields:
|
||||
label = f
|
||||
if not f in msg.keys():
|
||||
f = f.upper()
|
||||
if not f in msg.keys():
|
||||
continue
|
||||
try:
|
||||
key = re.sub("-", "_", f).lower()
|
||||
value = getattr(IMAPheader, "parse_%s" % key)(msg[f], full=addrfull)
|
||||
self.headers += [{"name" : label, "value" : value}]
|
||||
except AttributeError:
|
||||
self.headers += [{"name" : label, "value" : msg[f]}]
|
||||
try:
|
||||
label = re.sub("-", "_", label)
|
||||
setattr(self, label, value)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import sys
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import simplejson
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf.urls.defaults import include
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from mailng.admin.models import Mailbox
|
||||
from mailng.lib import events, parameters, _render, _render_error, getctx
|
||||
from imap_listing import *
|
||||
from forms import *
|
||||
from templatetags.webextras import *
|
||||
from imap_listing import ImapEmail
|
||||
|
||||
def init():
|
||||
events.register("UserMenuDisplay", menu)
|
||||
events.register("UserLogin", userlogin)
|
||||
parameters.register("webmail", "IMAP_SERVER", "string", "127.0.0.1",
|
||||
help=_("Address of your IMAP server"))
|
||||
parameters.register("webmail", "IMAP_SECURED", "list_yesno", "no",
|
||||
help=_("Use a secured connection to access IMAP server"))
|
||||
parameters.register("webmail", "IMAP_PORT", "int", "143",
|
||||
help=_("Listening port of your IMAP server"))
|
||||
parameters.register("webmail", "SMTP_SERVER", "string", "127.0.0.1",
|
||||
help=_("Address of your SMTP server"))
|
||||
parameters.register("webmail", "SMTP_PORT", "int", "25",
|
||||
help=_("Listening port of your SMTP server"))
|
||||
parameters.register("webmail", "SMTP_AUTHENTICATION", "list_yesno", "no",
|
||||
help=_("Server needs authentication"))
|
||||
parameters.register("webmail", "SMTP_SECURED", "list_yesno", "no",
|
||||
help=_("Use a secured connection to access SMTP server"))
|
||||
|
||||
|
||||
def urls():
|
||||
return (r'^mailng/webmail/',
|
||||
include('mailng.extensions.webmail.urls'))
|
||||
|
||||
def menu(**kwargs):
|
||||
if kwargs["target"] != "user_menu_box":
|
||||
return []
|
||||
return [
|
||||
{"name" : _("Webmail"),
|
||||
"url" : reverse(index),
|
||||
"img" : "/static/pics/webmail.png"}
|
||||
]
|
||||
|
||||
def userlogin(**kwargs):
|
||||
kwargs["request"].session["password"] = kwargs["password"]
|
||||
|
||||
@login_required
|
||||
def folder(request, name, updatenav=True):
|
||||
if not name:
|
||||
name = "INBOX"
|
||||
order = request.GET.has_key("order") and request.GET["order"] or "-date"
|
||||
if updatenav:
|
||||
pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1
|
||||
if not "navparams" in request.session.keys():
|
||||
request.session["navparams"] = {}
|
||||
request.session["folder"] = name
|
||||
request.session["page"] = pageid
|
||||
if order:
|
||||
request.session["navparams"]["order"] = order
|
||||
else:
|
||||
# Internal usage
|
||||
order = request.session["navparams"]["order"]
|
||||
|
||||
lst = ImapListing(request.user.username, request.session["password"],
|
||||
baseurl=name, folder=name, order=order)
|
||||
page = lst.paginator.getpage(request.session["page"])
|
||||
if page:
|
||||
content = lst.fetch(request, page.id_start, page.id_stop)
|
||||
navbar = lst.render_navbar(page)
|
||||
else:
|
||||
content = "Empty folder"
|
||||
navbar = ""
|
||||
ctx = getctx("ok", listing=content, navbar=navbar,
|
||||
menu=listing_menu("", name, request.user.get_all_permissions()))
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
try:
|
||||
navp = request.session.has_key("navparams") \
|
||||
and request.session["navparams"] or {}
|
||||
lst = ImapListing(request.user.username, request.session["password"],
|
||||
baseurl="INBOX", navparams=navp, folder="INBOX",
|
||||
empty=True)
|
||||
except Exception as exp:
|
||||
return _render_error(request, {"error" : exp})
|
||||
return lst.render(request)
|
||||
|
||||
def fetchmail(request, folder, mail_id, all=False):
|
||||
res = IMAPconnector(request).fetch(start=mail_id, folder=folder, all=all)
|
||||
if len(res):
|
||||
return res[0]
|
||||
return None
|
||||
|
||||
@login_required
|
||||
def viewmail(request, folder, mail_id=None):
|
||||
from templatetags.webextras import viewm_menu
|
||||
|
||||
content = Template("""
|
||||
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
|
||||
""").render(Context({"url" : reverse(getmailcontent, args=[folder, mail_id])}))
|
||||
menu = viewm_menu("", request.session, mail_id,
|
||||
request.user.get_all_permissions())
|
||||
ctx = getctx("ok", menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def getmailcontent(request, folder, mail_id):
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
IMAPconnector(request).msgseen(msg["imapid"])
|
||||
email = ImapEmail(msg, mode="html", links="1")
|
||||
try:
|
||||
pageid = request.session["page"]
|
||||
except KeyError:
|
||||
pageid = "1"
|
||||
folder, imapid = msg["imapid"].split("/")
|
||||
return _render(request, "common/viewmail.html", {
|
||||
"headers" : email.render_headers(folder, mail_id),
|
||||
"folder" : folder, "imapid" : imapid, "mailbody" : email.body,
|
||||
"pre" : email.pre
|
||||
})
|
||||
|
||||
@login_required
|
||||
def getattachment(request, folder, mail_id):
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
for part in msg.walk():
|
||||
fname = part.get_filename()
|
||||
if fname is None:
|
||||
continue
|
||||
decoded = decode_header(fname)
|
||||
if decoded[0][1] is None:
|
||||
fname = unicode(decoded[0][0])
|
||||
else:
|
||||
fname = unicode(decoded[0][0], decoded[0][1])
|
||||
if fname == request.GET["fname"]:
|
||||
resp = HttpResponse(part.get_payload(decode=1))
|
||||
for hdr in ["Content-Disposition", "Content-Type"]:
|
||||
resp[hdr] = re.sub("\s", "", part[hdr])
|
||||
resp["Content-Length"] = len(resp.content)
|
||||
return resp
|
||||
raise Http404
|
||||
|
||||
@login_required
|
||||
def move(request):
|
||||
for arg in ["msgset", "to"]:
|
||||
if not request.GET.has_key(arg):
|
||||
return
|
||||
msgset = []
|
||||
fdname = ""
|
||||
for item in request.GET["msgset"].split(","):
|
||||
fdname, id = item.split("/")
|
||||
msgset += [id]
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.move(",".join(msgset), fdname, request.GET["to"])
|
||||
return folder(request, fdname, False)
|
||||
|
||||
@login_required
|
||||
def delete(request, fdname, mail_id):
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.move(mail_id, fdname, "Trash")
|
||||
return folder(request, fdname, False)
|
||||
|
||||
@login_required
|
||||
def empty(request, name):
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.empty(name)
|
||||
return folder(request, name, False)
|
||||
|
||||
def render_compose(request, form, posturl, bodyheader=None, body=None):
|
||||
menu = compose_menu("", request.session, request.user.get_all_permissions())
|
||||
content = render_to_string("webmail/compose.html", {
|
||||
"form" : form, "bodyheader" : bodyheader, "body" : body,
|
||||
"posturl" : posturl
|
||||
})
|
||||
ctx = getctx("ok", level=2, menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
def send_mail(request, withctx=False):
|
||||
form = ComposeMailForm(request.POST)
|
||||
if form.is_valid():
|
||||
from email.mime.text import MIMEText
|
||||
import smtplib
|
||||
|
||||
msg = MIMEText(request.POST["id_body"].encode("utf-8"),
|
||||
_charset="utf-8")
|
||||
msg["Subject"] = request.POST["subject"]
|
||||
msg["From"] = request.POST["from_"]
|
||||
msg["To"] = request.POST["to"]
|
||||
rcpts = msg['To'].split(',')
|
||||
if "cc" in request.POST.keys():
|
||||
msg["Cc"] = request.POST["cc"]
|
||||
rcpts += msg["Cc"].split(",")
|
||||
try:
|
||||
s = smtplib.SMTP(parameters.get("webmail", "SMTP_SERVER"))
|
||||
if parameters.get("webmail", "SMTP_SECURED") == "yes":
|
||||
s.starttls()
|
||||
except (smtplib.SMTPException, ssl.SSLError), error:
|
||||
print error
|
||||
# Prévoir la remontée de cette erreur au niveau du client!!
|
||||
|
||||
if parameters.get("webmail", "SMTP_AUTHENTICATION") == "yes":
|
||||
s.login(request.user.username, request.session["password"])
|
||||
s.sendmail(msg['From'], rcpts, msg.as_string())
|
||||
s.quit()
|
||||
ctx = getctx("ok", url="%s?page=%s" % (request.session["folder"],
|
||||
request.session["page"]))
|
||||
else:
|
||||
ctx = getctx("ko", level=2, listing=render_to_string("webmail/compose.html",
|
||||
{"form" : form}))
|
||||
if not withctx:
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
return ctx, HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def compose(request):
|
||||
if request.method == "POST":
|
||||
return send_mail(request)
|
||||
|
||||
form = ComposeMailForm()
|
||||
form.fields["from_"].initial = request.user.username
|
||||
return render_compose(request, form, reverse(compose))
|
||||
|
||||
@login_required
|
||||
def reply(request, folder, mail_id):
|
||||
if request.method == "POST":
|
||||
return send_mail(request)
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
email = ImapEmail(msg, True)
|
||||
lines = email.body.split('\n')
|
||||
body = ""
|
||||
for l in lines:
|
||||
if body != "":
|
||||
body += "\n"
|
||||
body += ">%s" % l
|
||||
form = ComposeMailForm()
|
||||
form.fields["from_"].initial = request.user.username
|
||||
if not "Reply-To" in msg.keys():
|
||||
form.fields["to"].initial = email.From
|
||||
else:
|
||||
form.fields["to"].initial = email.Reply_To
|
||||
if request.GET.has_key("all"):
|
||||
form.fields["cc"].initial = ""
|
||||
toparse = msg["To"].split(",")
|
||||
if "Cc" in msg.keys():
|
||||
toparse += msg["Cc"].split(",")
|
||||
for addr in toparse:
|
||||
tmp = EmailAddress(addr)
|
||||
if tmp.address and tmp.address == request.user.username:
|
||||
continue
|
||||
if form.fields["cc"].initial != "":
|
||||
form.fields["cc"].initial += ", "
|
||||
form.fields["cc"].initial += tmp.fulladdress
|
||||
m = re.match("re\s*:\s*.+", email.Subject.lower())
|
||||
if m:
|
||||
form.fields["subject"].initial = email.Subject
|
||||
else:
|
||||
form.fields["subject"].initial = "Re: %s" % email.Subject
|
||||
textheader = "%s %s" % (email.From, _("wrote:"))
|
||||
return render_compose(request, form, reverse(reply, args=[folder, mail_id]),
|
||||
textheader, body)
|
||||
|
||||
@login_required
|
||||
def forward(request, folder, mail_id):
|
||||
if request.method == "POST":
|
||||
ctx, response = send_mail(request, True)
|
||||
if ctx["status"] == "ok":
|
||||
IMAPconnector(request).msgforwarded(folder, mail_id)
|
||||
return response
|
||||
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
email = ImapEmail(msg, True)
|
||||
textheader = "----- %s -----" % _("Original message")
|
||||
textheader += "\n%s: %s" % (_("Subject"), email.Subject)
|
||||
textheader += "\n%s: %s" % (_("Date"), msg["Date"])
|
||||
for hdr in ["From", "To", "Reply-To"]:
|
||||
try:
|
||||
key = re.sub("-", "_", hdr)
|
||||
value = getattr(email, key)
|
||||
textheader += "\n%s: %s" % (_(hdr), value)
|
||||
except:
|
||||
pass
|
||||
textheader += "\n"
|
||||
|
||||
form = ComposeMailForm()
|
||||
form.fields["from_"].initial = request.user.username
|
||||
form.fields["subject"].initial = "Fwd: %s" % email.Subject
|
||||
return render_compose(request, form, reverse(forward, args=[folder, mail_id]),
|
||||
textheader, email.body)
|
||||
@@ -0,0 +1,54 @@
|
||||
{% load i18n %}
|
||||
<style type="text/css">
|
||||
#compose-container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
div.row {
|
||||
margin: 3px;
|
||||
clear: both;
|
||||
}
|
||||
div.row label {
|
||||
float: left;
|
||||
width: 120px;
|
||||
font-size: small;
|
||||
padding: 3px 0;
|
||||
}
|
||||
textarea {
|
||||
border: 1px solid #cccccc;
|
||||
background-color: #eee;
|
||||
width: 80%;
|
||||
height: 30px;
|
||||
font-size: medium;
|
||||
}
|
||||
textarea:hover {
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
#id_body {
|
||||
background: white;
|
||||
width: 98%;
|
||||
}
|
||||
input {
|
||||
width: 80%;
|
||||
padding: 0;
|
||||
}
|
||||
#mailheader {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<form method="POST" action="{{ posturl }}" id="composemail">
|
||||
<div id="compose-container">
|
||||
<div id="mailheader">
|
||||
{% include "common/genericform.html" %}
|
||||
</div>
|
||||
{% if body %}
|
||||
<textarea id="id_body" name="id_body">
|
||||
{% if bodyheader %}{{ bodyheader }}{% endif %}
|
||||
{{ body }}
|
||||
</textarea>
|
||||
{% else %}
|
||||
<textarea id="id_body" name="id_body"></textarea>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,17 @@
|
||||
{% load i18n %}
|
||||
<table id="emailheaders">
|
||||
{% for hdr in headers %}
|
||||
<tr><th width="130px">{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
|
||||
{% endfor %}
|
||||
{% if attachments %}
|
||||
<tr>
|
||||
<th>{% trans "Attachments" %}</th>
|
||||
<td>
|
||||
{% url extensions.webmail.main.getattachment folder mail_id as test %}
|
||||
{% for attachment in attachments %}
|
||||
<a href="{{ test }}?fname={{ attachment }}">{{ attachment }}</a>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
@@ -0,0 +1,363 @@
|
||||
{% extends "common/email_listing.html" %}
|
||||
{% load i18n %}
|
||||
{% block css %}
|
||||
#listing {
|
||||
position: absolute;
|
||||
left: 155px;
|
||||
right: 0px;
|
||||
top: 130px;
|
||||
height: expression(document.body.clientHeight - 35 + "px");
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
html > body #listing {
|
||||
position: fixed;
|
||||
bottom: 35px;
|
||||
}
|
||||
|
||||
#leftcol {
|
||||
float: left;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
thead tr {
|
||||
position: relative;
|
||||
top: expression(offsetParent.scrollTop); /*IE5+ only*/
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
table#emails > tbody {
|
||||
position: fixed;
|
||||
bottom: 35px;
|
||||
top: 155px;
|
||||
left: 155px;
|
||||
right: 0px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#emails th {
|
||||
/*text-align: center;*/
|
||||
}
|
||||
|
||||
#emails tr {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.box h3 {
|
||||
font-size: small;
|
||||
margin: 2px 0px 2px 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box ul {
|
||||
list-style: none;
|
||||
padding: 5px 5px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.box li {
|
||||
clear: both;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.box li.selected {
|
||||
background: #ffffcc;
|
||||
}
|
||||
|
||||
.box a:link, .box a:visited {
|
||||
background: transparent;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.box a:hover {
|
||||
color: #6AB0C5;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.box img {
|
||||
float: left;
|
||||
margin-right: 2px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
/*-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
|
||||
-webkit-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;*/
|
||||
}
|
||||
|
||||
th.selected {
|
||||
background: #EEEEEE;
|
||||
}
|
||||
|
||||
.draggable {2
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
background: #6AB0C5;
|
||||
}
|
||||
|
||||
tr {
|
||||
position: relative;
|
||||
border-bottom: 1px solid #C8D6E8;
|
||||
}
|
||||
|
||||
tr.selected {
|
||||
background: #ffffcc;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
wm_updatelisting = function(resp) {
|
||||
updatelisting(resp);
|
||||
|
||||
$$("a[name=compose]").addEvent("click", loadCompose);
|
||||
$$("a[name=empty]").addEvent("click", empty);
|
||||
if (!checkSelection()) {
|
||||
select($$("a[href=INBOX]"));
|
||||
}
|
||||
}
|
||||
|
||||
empty = function(event) {
|
||||
event.stop();
|
||||
new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) {
|
||||
wm_updatelisting(resp);
|
||||
}}).get();
|
||||
}
|
||||
|
||||
checkSelection = function() {
|
||||
var selection = false;
|
||||
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
if (!selection) {
|
||||
selection = item.getParent().hasClass("selected");
|
||||
}
|
||||
});
|
||||
return selection;
|
||||
}
|
||||
|
||||
select = function(obj) {
|
||||
$$("a[name=loadfolder]").each(function(item) {
|
||||
item.getParent().removeClass("selected");
|
||||
item.getParent().addClass("droppable");
|
||||
});
|
||||
obj.getParent().removeClass("droppable");
|
||||
obj.getParent().addClass("selected");
|
||||
}
|
||||
|
||||
register_callback("viewmail", function(resp) {
|
||||
wm_updatelisting(resp, "hidden");
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=reply]").addEvent("click", reply);
|
||||
$$("a[name=replyall]").addEvent("click", replyall);
|
||||
$$("a[name=forward]").addEvent("click", forward);
|
||||
$$("a[name=delete]").addEvent("click", function(event) {
|
||||
var lnk = event.target;
|
||||
|
||||
event.stop();
|
||||
new Request.JSON({url: lnk.get("href"),
|
||||
onSuccess: get_callback("folder")}).get();
|
||||
});
|
||||
setDivHeight("mailcontent", 5, 0);
|
||||
});
|
||||
|
||||
compose_callback = function(resp) {
|
||||
wm_updatelisting(resp);
|
||||
window.addEvent("resize", function(evt) {
|
||||
setDivHeight("id_body", $("mailheader").getSize().y, 0);
|
||||
});
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=sendmail]").addEvent("click", function(evt) {
|
||||
evt.stop();
|
||||
$("composemail").set("send", {
|
||||
onSuccess: function(resp) {
|
||||
resp = JSON.decode(resp);
|
||||
if (resp.status == "ko") {
|
||||
wm_updatelisting(resp);
|
||||
window.fireEvent("resize");
|
||||
return;
|
||||
}
|
||||
current_anchor.parse_string(resp.url, true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
}
|
||||
});
|
||||
$("composemail").send();
|
||||
});
|
||||
window.fireEvent("resize");
|
||||
}
|
||||
|
||||
loadCompose = function(event) {
|
||||
event.stop();
|
||||
current_anchor.baseurl("compose/").update();
|
||||
}
|
||||
register_callback("compose", compose_callback);
|
||||
|
||||
reply = function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href");
|
||||
}
|
||||
register_callback("reply", compose_callback);
|
||||
replyall = function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href") + "?all=1";
|
||||
}
|
||||
register_callback("replyall", compose_callback);
|
||||
forward = function(event) {
|
||||
event.stop();
|
||||
location.hash += event.target.get("href");
|
||||
}
|
||||
register_callback("forward", compose_callback);
|
||||
|
||||
loadFolder = function(event, obj) {
|
||||
if (!$defined(obj)) {
|
||||
obj = event.target;
|
||||
}
|
||||
event.stop();
|
||||
current_anchor.parse_string(obj.get("href"), true).setparams(navparams);
|
||||
current_anchor.update();
|
||||
}
|
||||
register_callback("folder", function(resp) {
|
||||
wm_updatelisting(resp);
|
||||
if (navparams.has("order")) {
|
||||
src = navparams.get("order").charAt(0) == "-" ? "down_arrow.png" : "up_arrow.png";
|
||||
th = $$("th[id=" + navparams.get("order").substring(1) + "]");
|
||||
th.getElement("img").set("src", "/static/pics/" + src);
|
||||
th.addClass("selected");
|
||||
}
|
||||
var selectedRows = new Array();
|
||||
$$("tr").addEvent("click", function(event) {
|
||||
var tr = event.target.getParent();
|
||||
if (!tr.hasClass("selected")) {
|
||||
tr.addClass("selected");
|
||||
selectedRows.include(tr);
|
||||
} else {
|
||||
tr.removeClass("selected");
|
||||
selectedRows.erase(tr);
|
||||
}
|
||||
});
|
||||
|
||||
$$("tr").each(function(item) {
|
||||
new Drag.Move(item, {
|
||||
droppables: ".droppable",
|
||||
}).addEvents({
|
||||
"start" : function(item) {
|
||||
if (!item.hasClass("selected")) {
|
||||
selectedRows.each(function(item, index) {
|
||||
item.removeClass("selected");
|
||||
});
|
||||
selectedRows.empty();
|
||||
item.addClass("selected");
|
||||
selectedRows.include(item);
|
||||
}
|
||||
var block = new Element("div", {id: "draggeditems"})
|
||||
.setStyles({"position" : "absolute",
|
||||
"opacity" : 0.8,
|
||||
"width" : "200px",
|
||||
"padding" : "5px",
|
||||
"text-align" : "center",
|
||||
"height" : "25px",
|
||||
"border" : "1px solid #333",
|
||||
"background" : "white"})
|
||||
.setStyles(item.getPosition())
|
||||
.inject(document.body);
|
||||
block.set("html", "Moving " + selectedRows.length + " messages");
|
||||
this.oldelement = this.element;
|
||||
this.element = block;
|
||||
},
|
||||
"cancel" : function(item) {
|
||||
if ($defined(this.oldelement)) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
this.element = this.oldelement;
|
||||
}
|
||||
},
|
||||
"enter" : function(item, droppable) {
|
||||
droppable.addClass("dragging");
|
||||
},
|
||||
"leave" : function(item, droppable) {
|
||||
droppable.removeClass("dragging");
|
||||
},
|
||||
"drop" : function(item, droppable, event) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
if (!droppable) {
|
||||
this.element = this.oldelement;
|
||||
return;
|
||||
}
|
||||
droppable.removeClass("dragging");
|
||||
dest = droppable.getFirst("a").get("href");
|
||||
msgset = new Array();
|
||||
selectedRows.each(function(item, index) {
|
||||
msgset.include(item.get("id"));
|
||||
});
|
||||
{% url extensions.webmail.main.move as moveurl %}
|
||||
new Request.JSON({url: "{{ moveurl }}",
|
||||
onSuccess: get_callback("folder")}
|
||||
).get({msgset : msgset.join(), to : dest});
|
||||
},
|
||||
});
|
||||
});
|
||||
$("toggleselect").addEvent("click", function(evt) {
|
||||
var checked = evt.target.get("checked");
|
||||
if (checked) {
|
||||
$$("tbody>tr").each(function(item) {
|
||||
item.addClass("selected");
|
||||
selectedRows.include(item);
|
||||
});
|
||||
} else {
|
||||
$$("tr").each(function(item) { item.removeClass("selected"); });
|
||||
selectedRows.empty();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$$("tr").addEvent("dblclick", viewmail);
|
||||
$$("th[class*=sortable]").addEvent("click", function(evt) {
|
||||
var tmp = "?";
|
||||
var order = (navparams.has("order")) ? navparams.get("order") : "";
|
||||
|
||||
if (order == "") {
|
||||
order = current_anchor.getparam("order");
|
||||
}
|
||||
if (order.substring(1) == evt.target.id) {
|
||||
var sign = (order.charAt(0) == '+') ? '-' : '+';
|
||||
order = sign + evt.target.id;
|
||||
} else {
|
||||
order = "-" + evt.target.id;
|
||||
}
|
||||
navparams.set("order", order);
|
||||
current_anchor.setparams(navparams).update();
|
||||
});
|
||||
});
|
||||
|
||||
foldercallback = function(event) {
|
||||
select(event.target);
|
||||
loadFolder(event, event.target);
|
||||
}
|
||||
$$("a[name=loadfolder]").addEvent("click", foldercallback);
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
|
||||
{% block leftcol %}
|
||||
<div id="leftcol" class="box">
|
||||
<h3>{% trans "Folders" %}</h3>
|
||||
<ul>
|
||||
{% for fd in folders %}
|
||||
{% url extensions.webmail.main.folder fd.name as fdurl %}
|
||||
<li class="droppable">
|
||||
<a href="{{ fd.name }}" name="loadfolder">
|
||||
{% if fd.icon %}<img src="/static/pics/{{ fd.icon }}" />
|
||||
{% else %}<img src="/static/pics/folder.png" />
|
||||
{% endif %}
|
||||
{{ fd.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block menu %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,100 @@
|
||||
from django import template
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.urlresolvers import reverse
|
||||
from mailng.extensions import webmail
|
||||
from mailng.extensions.webmail.imap_listing import IMAPheader
|
||||
|
||||
register = template.Library()
|
||||
|
||||
def backurl(session, **kwargs):
|
||||
res = session["folder"]
|
||||
params = ""
|
||||
for key in ["page"]:
|
||||
if key in session.keys():
|
||||
if params != "":
|
||||
params += "&"
|
||||
params += "%s=%s" % (key, session[key])
|
||||
for k, v in kwargs.iteritems():
|
||||
if params != "":
|
||||
params += "&"
|
||||
params += "%s=%s" % (k, v)
|
||||
if params != "":
|
||||
res += "?%s" % params
|
||||
return res
|
||||
|
||||
@register.simple_tag
|
||||
def viewm_menu(selection, session, mail_id, perms):
|
||||
entries = [
|
||||
{"name" : "back",
|
||||
"url" : "%s?page=%s" % (session["folder"], session["page"]),
|
||||
"img" : "/static/pics/back.png",
|
||||
"label" : _("Back")},
|
||||
{"name" : "reply",
|
||||
"url" : "reply/",
|
||||
"img" : "/static/pics/reply.png",
|
||||
"label" : _("Reply")},
|
||||
{"name" : "replyall",
|
||||
"url" : "reply/",
|
||||
"img" : "/static/pics/reply-all.png",
|
||||
"label" : _("Reply all")},
|
||||
{"name" : "forward",
|
||||
"url" : "forward/",
|
||||
"img" : "/static/pics/alias.png",
|
||||
"label" : _("Forward")},
|
||||
{"name" : "delete",
|
||||
"img" : "/static/pics/remove.png",
|
||||
"url" : reverse(webmail.main.delete, args=[session["folder"], mail_id]),
|
||||
"label" : _("Delete")},
|
||||
]
|
||||
|
||||
return render_to_string('main/menu.html',
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def compose_menu(selection, session, perms):
|
||||
entries = [
|
||||
{"name" : "back",
|
||||
"url" : "%s?page=%s" % (session["folder"], session["page"]),
|
||||
"img" : "/static/pics/back.png",
|
||||
"label" : _("Back")},
|
||||
{"name" : "sendmail",
|
||||
"url" : "",
|
||||
"img" : "/static/pics/send-receive.png",
|
||||
"label" : _("Send")},
|
||||
]
|
||||
return render_to_string('main/menu.html',
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
|
||||
@register.simple_tag
|
||||
def listing_menu(selection, folder, perms):
|
||||
entries = [
|
||||
{"name" : "compose",
|
||||
"url" : reverse(webmail.main.compose),
|
||||
"img" : "/static/pics/edit.png",
|
||||
"label" : _("New message")},
|
||||
{"name" : "actions",
|
||||
"img" : "/static/pics/domains.png",
|
||||
"label" : _("Mark messages"),
|
||||
"class" : "menubardropdown",
|
||||
"menu" : [
|
||||
{"name" : "mark-read",
|
||||
"label" : _("As read")},
|
||||
{"name" : "mark-unread",
|
||||
"label" : _("As unread")}
|
||||
]
|
||||
}
|
||||
]
|
||||
if folder in ["Trash"]:
|
||||
entries += [
|
||||
{"name" : "empty",
|
||||
"url" : reverse(webmail.main.empty, args=[folder]),
|
||||
"img" : "/static/pics/clear.png",
|
||||
"label" : _("Empty trash")}
|
||||
]
|
||||
|
||||
return render_to_string("main/menu.html",
|
||||
{"selection" : selection, "entries" : entries,
|
||||
"perms" : perms})
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.webmail.main',
|
||||
(r'^$', "index"),
|
||||
(r'^compose/$', "compose"),
|
||||
(r'^move/$', "move"),
|
||||
(r'^empty/(?P<name>.+)/', "empty"),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/part/$', 'getattachment'),
|
||||
(r'^(?P<fdname>.+)/(?P<mail_id>[\w\-\+]+)/delete/$', 'delete'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/forward/$', 'forward'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/reply/$', 'reply'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
(r'^(?P<name>.+)/$', "folder"),
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import popen2
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
@@ -41,6 +42,10 @@ def _render(request, tpl, user_context):
|
||||
return render_to_response(tpl, user_context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def _render_error(request, user_context):
|
||||
return render_to_response("common/error.html", user_context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
def exec_as_vuser(cmd):
|
||||
code, output = exec_pipe("sudo -u %s %s" % (settings.VIRTUAL_UID, cmd))
|
||||
if code:
|
||||
@@ -54,7 +59,17 @@ def _ctx_ok(url):
|
||||
def _ctx_ko(tpl, ctx):
|
||||
return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
|
||||
|
||||
def decode(s, encodings=('ascii', 'utf8', 'latin1')):
|
||||
def getctx(status, level=1, callback=None, **kwargs):
|
||||
if not callback:
|
||||
callername = sys._getframe(level).f_code.co_name
|
||||
else:
|
||||
callername = callback
|
||||
ctx = {"status" : status, "callback" : callername}
|
||||
for kw, v in kwargs.iteritems():
|
||||
ctx[kw] = v
|
||||
return ctx
|
||||
|
||||
def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
|
||||
for encoding in encodings:
|
||||
try:
|
||||
return s.decode(encoding)
|
||||
@@ -69,3 +84,14 @@ def getoption(name, default=None):
|
||||
except AttributeError:
|
||||
res = default
|
||||
return res
|
||||
|
||||
|
||||
class Singleton(type):
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(Singleton, cls).__init__(name, bases, dict)
|
||||
cls.instance = None
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls.instance is None:
|
||||
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
|
||||
return cls.instance
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import re
|
||||
from email.header import decode_header
|
||||
import lxml
|
||||
import lxml.html
|
||||
from lxml import etree
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Template, Context
|
||||
from django.utils.translation import ugettext as _
|
||||
from mailng.lib import _render, decode
|
||||
|
||||
attached_map = {}
|
||||
|
||||
class MBconnector(object):
|
||||
def __init__(self, address, port):
|
||||
self.address = address
|
||||
self.port = port
|
||||
|
||||
def messages_count(self, **kwargs):
|
||||
pass
|
||||
|
||||
class Page(object):
|
||||
def __init__(self, pageid, id_start, id_stop, items,
|
||||
items_per_page, has_previous, has_next,
|
||||
baseurl=None):
|
||||
self.pageid = pageid
|
||||
self.id_start = id_start
|
||||
self.id_stop = id_stop
|
||||
self.items = items
|
||||
self.items_per_page = items_per_page
|
||||
self.has_previous = has_previous
|
||||
self.has_next = has_next
|
||||
self.baseurl = baseurl
|
||||
|
||||
def previous_page(self):
|
||||
if not self.has_previous:
|
||||
return False
|
||||
return self.pageid - 1
|
||||
|
||||
def next_page(self):
|
||||
if not self.has_next:
|
||||
return False
|
||||
return self.pageid + 1
|
||||
|
||||
def last_page(self):
|
||||
lid = self.items / self.items_per_page
|
||||
if not lid:
|
||||
return 1
|
||||
if self.items % self.items_per_page:
|
||||
lid += 1
|
||||
return lid
|
||||
|
||||
class Paginator(object):
|
||||
def __init__(self, total, elems_per_page):
|
||||
self.total = total
|
||||
self.elems_per_page = elems_per_page
|
||||
self.baseurl = None
|
||||
|
||||
def _indexes(self, page):
|
||||
id_start = self.elems_per_page * page + 1
|
||||
id_stop = id_start + self.elems_per_page - 1
|
||||
return (id_start, id_stop)
|
||||
|
||||
def getpage(self, page):
|
||||
if page < 1:
|
||||
return None
|
||||
id_start, id_stop = self._indexes(page - 1)
|
||||
has_previous = has_next = False
|
||||
if id_start < self.total:
|
||||
if page > 1:
|
||||
has_previous = True
|
||||
if id_stop < self.total:
|
||||
has_next = True
|
||||
else:
|
||||
id_stop = self.total
|
||||
else:
|
||||
return None
|
||||
p = Page(page, id_start, id_stop, self.total,
|
||||
self.elems_per_page, has_previous, has_next)
|
||||
if self.baseurl:
|
||||
p.baseurl = self.baseurl
|
||||
return p
|
||||
|
||||
class EmailListing:
|
||||
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
|
||||
navparams={}, **kwargs):
|
||||
self.folder = folder
|
||||
self.elems_per_page = elems_per_page
|
||||
self.navparams = navparams
|
||||
order = "order" in kwargs.keys() and kwargs["order"] or None
|
||||
self.empty = "empty" in kwargs.keys() and kwargs["empty"] or False
|
||||
if not self.empty:
|
||||
self.paginator = Paginator(self.mbc.messages_count(folder=self.folder,
|
||||
order=order),
|
||||
elems_per_page)
|
||||
if baseurl:
|
||||
self.paginator.baseurl = baseurl
|
||||
|
||||
def render_navbar(self, page):
|
||||
if page is None:
|
||||
return ""
|
||||
return render_to_string("common/navbar.html", {
|
||||
"page" : page
|
||||
})
|
||||
|
||||
def getfolders(self):
|
||||
return None
|
||||
|
||||
def fetch(self, request, id_start, id_stop):
|
||||
table = self.tbltype(self.mbc.fetch(start=id_start, stop=id_stop,
|
||||
folder=self.folder))
|
||||
tpl = Template("""
|
||||
<form method="POST" id="listingform">
|
||||
{{ table }}
|
||||
</form>""")
|
||||
return tpl.render(Context({"table" : table.render(request)}))
|
||||
|
||||
def render(self, request, pageid=1, **kwargs):
|
||||
listing = ""
|
||||
page = None
|
||||
if not self.empty:
|
||||
page = self.paginator.getpage(pageid)
|
||||
if not page:
|
||||
listing = "Empty folder"
|
||||
else:
|
||||
listing = self.fetch(request, page.id_start, page.id_stop)
|
||||
elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
|
||||
return _render(request, self.tpl, {
|
||||
"listing" : listing,
|
||||
"elapsed" : elapsed,
|
||||
"navbar" : self.render_navbar(page),
|
||||
"folders" : self.getfolders(),
|
||||
"selection" : self.folder,
|
||||
"navparams" : self.navparams,
|
||||
"deflocation" : self.deflocation, "defcallback" : self.defcallback
|
||||
})
|
||||
|
||||
class Email(object):
|
||||
def __init__(self, msg, mode="plain", links="0"):
|
||||
self.attached_map = {}
|
||||
contents = {"html" : "", "plain" : ""}
|
||||
self.headers = []
|
||||
self.attachments = []
|
||||
for part in msg.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
if part.has_key("Content-Disposition"):
|
||||
if re.match("^attachment", part["Content-Disposition"]):
|
||||
self.attachments += [part]
|
||||
if part.get_content_type() in ("text/html", "text/plain"):
|
||||
contents[part.get_content_subtype()] += part.get_payload(decode=True)
|
||||
if mode != "html" or links == "0":
|
||||
continue
|
||||
|
||||
if part.get_content_maintype() == "image":
|
||||
if part.has_key("Content-Location"):
|
||||
fname = part["Content-Location"]
|
||||
if re.match("^http:", fname):
|
||||
path = fname
|
||||
else:
|
||||
path = "/%s" % os.path.join("static/tmp", fname)
|
||||
fp = open(path, "wb")
|
||||
fp.write(part.get_payload(decode=True))
|
||||
fp.close()
|
||||
m = re.match("<(.+)>", part["Content-ID"])
|
||||
if m:
|
||||
self.attached_map[m.group(1)] = path
|
||||
else:
|
||||
self.attached_map[part["Content-ID"]] = path
|
||||
|
||||
if not contents.has_key(mode) or contents[mode] == "":
|
||||
if mode == "html":
|
||||
mode = "plain"
|
||||
else:
|
||||
mode = "html"
|
||||
self.pre, self.body = \
|
||||
getattr(self, "viewmail_%s" % mode)(contents[mode], links=links)
|
||||
|
||||
def map_cid(self, url):
|
||||
import re
|
||||
|
||||
map = globals()["attached_map"]
|
||||
m = re.match(".*cid:([^\"]+)", url)
|
||||
if m:
|
||||
if map.has_key(m.group(1)):
|
||||
return map[m.group(1)]
|
||||
return url
|
||||
|
||||
def render_headers(self, folder, mail_id):
|
||||
attachments = []
|
||||
for part in self.attachments:
|
||||
decoded = decode_header(part.get_filename())
|
||||
if decoded[0][1] is None:
|
||||
attachments += [decoded[0][0]]
|
||||
else:
|
||||
attachments += [unicode(decoded[0][0], decoded[0][1])]
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : self.headers,
|
||||
"folder" : folder, "mail_id" : mail_id,
|
||||
"attachments" : attachments != [] and attachments or None
|
||||
})
|
||||
|
||||
def render(self, request):
|
||||
return _render(request, 'common/getmailcontent.html', {
|
||||
"body" : self.body, "pre" : self.pre
|
||||
})
|
||||
|
||||
def viewmail_plain(self, content, **kwargs):
|
||||
body = decode(content)
|
||||
return (True, body)
|
||||
|
||||
def viewmail_html(self, content, **kwargs):
|
||||
links = kwargs.has_key("links") and kwargs["links"] or "0"
|
||||
html = lxml.html.fromstring(content)
|
||||
if links == "0":
|
||||
html.rewrite_links(lambda x: None)
|
||||
else:
|
||||
html.rewrite_links(self.map_cid)
|
||||
body = html.find("body")
|
||||
if body is None:
|
||||
body = lxml.html.tostring(html)
|
||||
else:
|
||||
body = lxml.html.tostring(body)
|
||||
body = re.sub("<(/?)body", lambda m: "<%sdiv" % m.group(1), body)
|
||||
body = Template(decode(body)).render({})
|
||||
return (False, body)
|
||||
|
||||
@@ -13,7 +13,9 @@ events = ["CreateDomain",
|
||||
"ModifyMailbox",
|
||||
|
||||
"UserMenuDisplay",
|
||||
"AdminMenuDisplay"]
|
||||
"AdminMenuDisplay",
|
||||
|
||||
"UserLogin"]
|
||||
|
||||
callbacks = {}
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
# -*- coding: latin-1 -*-
|
||||
|
||||
"""
|
||||
Imap folder names are encoded using a special version of utf-7 as defined in RFC
|
||||
2060 section 5.1.3.
|
||||
|
||||
5.1.3. Mailbox International Naming Convention
|
||||
|
||||
By convention, international mailbox names are specified using a
|
||||
modified version of the UTF-7 encoding described in [UTF-7]. The
|
||||
purpose of these modifications is to correct the following problems
|
||||
with UTF-7:
|
||||
|
||||
1) UTF-7 uses the "+" character for shifting; this conflicts with
|
||||
the common use of "+" in mailbox names, in particular USENET
|
||||
newsgroup names.
|
||||
|
||||
2) UTF-7's encoding is BASE64 which uses the "/" character; this
|
||||
conflicts with the use of "/" as a popular hierarchy delimiter.
|
||||
|
||||
3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
|
||||
the use of "\" as a popular hierarchy delimiter.
|
||||
|
||||
4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
|
||||
the use of "~" in some servers as a home directory indicator.
|
||||
|
||||
5) UTF-7 permits multiple alternate forms to represent the same
|
||||
string; in particular, printable US-ASCII chararacters can be
|
||||
represented in encoded form.
|
||||
|
||||
In modified UTF-7, printable US-ASCII characters except for "&"
|
||||
represent themselves; that is, characters with octet values 0x20-0x25
|
||||
and 0x27-0x7e. The character "&" (0x26) is represented by the two-
|
||||
octet sequence "&-".
|
||||
|
||||
All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
|
||||
Unicode 16-bit octets) are represented in modified BASE64, with a
|
||||
further modification from [UTF-7] that "," is used instead of "/".
|
||||
Modified BASE64 MUST NOT be used to represent any printing US-ASCII
|
||||
character which can represent itself.
|
||||
|
||||
"&" is used to shift to modified BASE64 and "-" to shift back to US-
|
||||
ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
|
||||
is, a name that ends with a Unicode 16-bit octet MUST end with a "-
|
||||
").
|
||||
|
||||
For example, here is a mailbox name which mixes English, Japanese,
|
||||
and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
|
||||
|
||||
Found here:
|
||||
http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py
|
||||
|
||||
"""
|
||||
import binascii
|
||||
import codecs
|
||||
|
||||
# encoding
|
||||
|
||||
def modified_base64(s):
|
||||
s = s.encode('utf-16be')
|
||||
return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
|
||||
|
||||
def doB64(_in, r):
|
||||
if _in:
|
||||
r.append('&%s-' % modified_base64(''.join(_in)))
|
||||
del _in[:]
|
||||
|
||||
def encoder(s):
|
||||
r = []
|
||||
_in = []
|
||||
for c in s:
|
||||
ordC = ord(c)
|
||||
if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
|
||||
doB64(_in, r)
|
||||
r.append(c)
|
||||
elif c == '&':
|
||||
doB64(_in, r)
|
||||
r.append('&-')
|
||||
else:
|
||||
_in.append(c)
|
||||
doB64(_in, r)
|
||||
return (str(''.join(r)), len(s))
|
||||
|
||||
|
||||
# decoding
|
||||
|
||||
def modified_unbase64(s):
|
||||
b = binascii.a2b_base64(s.replace(',', '/') + '===')
|
||||
return unicode(b, 'utf-16be')
|
||||
|
||||
|
||||
def decoder(s):
|
||||
r = []
|
||||
decode = []
|
||||
for c in s:
|
||||
if c == '&' and not decode:
|
||||
decode.append('&')
|
||||
elif c == '-' and decode:
|
||||
if len(decode) == 1:
|
||||
r.append('&')
|
||||
else:
|
||||
r.append(modified_unbase64(''.join(decode[1:])))
|
||||
decode = []
|
||||
elif decode:
|
||||
decode.append(c)
|
||||
else:
|
||||
r.append(c)
|
||||
if decode:
|
||||
r.append(modified_unbase64(''.join(decode[1:])))
|
||||
bin_str = ''.join(r)
|
||||
return (bin_str, len(s))
|
||||
|
||||
|
||||
class StreamReader(codecs.StreamReader):
|
||||
def decode(self, s, errors='strict'):
|
||||
return decoder(s)
|
||||
|
||||
|
||||
class StreamWriter(codecs.StreamWriter):
|
||||
def decode(self, s, errors='strict'):
|
||||
return encoder(s)
|
||||
|
||||
|
||||
def imap4_utf_7(name):
|
||||
if name == 'imap4-utf-7':
|
||||
return (encoder, decoder, StreamReader, StreamWriter)
|
||||
codecs.register(imap4_utf_7)
|
||||
|
||||
|
||||
## testing methods
|
||||
|
||||
def imapUTF7Encode(ust):
|
||||
"Returns imap utf-7 encoded version of string"
|
||||
return ust.encode('imap4-utf-7')
|
||||
|
||||
def imapUTF7EncodeSequence(seq):
|
||||
"Returns imap utf-7 encoded version of strings in sequence"
|
||||
return [imapUTF7Encode(itm) for itm in seq]
|
||||
|
||||
|
||||
def imapUTF7Decode(st):
|
||||
"Returns utf7 encoded version of imap utf-7 string"
|
||||
return st.decode('imap4-utf-7')
|
||||
|
||||
def imapUTF7DecodeSequence(seq):
|
||||
"Returns utf7 encoded version of imap utf-7 strings in sequence"
|
||||
return [imapUTF7Decode(itm) for itm in seq]
|
||||
|
||||
|
||||
def utf8Decode(st):
|
||||
"Returns utf7 encoded version of imap utf-7 string"
|
||||
return st.decode('utf-8')
|
||||
|
||||
|
||||
def utf7SequenceToUTF8(seq):
|
||||
"Returns utf7 encoded version of imap utf-7 strings in sequence"
|
||||
return [itm.decode('imap4-utf-7').encode('utf-8') for itm in seq]
|
||||
|
||||
|
||||
__all__ = ['imapUTF7Encode', 'imapUTF7Decode', ]
|
||||
|
||||
if __name__=='__main__':
|
||||
|
||||
## print u'bøx'.encode('imap4-utf-7')
|
||||
## print 'expected b&APg-x'
|
||||
##
|
||||
## print u'båx'.encode('imap4-utf-7')
|
||||
## print 'expected b&AOU-x'
|
||||
##
|
||||
print '#######'
|
||||
print 'bøx'
|
||||
e = imapUTF7Encode(u'bøx')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print 'båx'
|
||||
e = imapUTF7Encode(u'båx')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print '~/bågø'
|
||||
e = imapUTF7Encode(u'~/bågø')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print 'Ting & Såger'
|
||||
e = imapUTF7Encode(u'Ting & Såger')
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
## e = imapUTF7Decode('b&AOU-x')
|
||||
## print e.encode('latin-1')
|
||||
##
|
||||
## e = imapUTF7Decode('b&APg-x')
|
||||
## print e.encode('latin-1')
|
||||
##
|
||||
print '#######'
|
||||
print '~/Følder/mailbåx & stuff + more'
|
||||
n = u'~/Følder/mailbåx & stuff + more'
|
||||
e = imapUTF7Encode(n)
|
||||
print e
|
||||
print imapUTF7Decode(e).encode('latin-1')
|
||||
|
||||
print '#######'
|
||||
print '~peter/mail/&ZeVnLIqe-/&U,BTFw-'
|
||||
print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import string
|
||||
from mailng.admin.models import Parameter
|
||||
|
||||
"""
|
||||
This interface provides a simple way to declare and store parameters
|
||||
in MailNG's database.
|
||||
|
||||
Core components or extensions can register their own parameters, which
|
||||
will be available and modifiable directly from the web interface.
|
||||
|
||||
Only super users will be able to acces this part of the web interface.
|
||||
"""
|
||||
|
||||
_params = {}
|
||||
|
||||
class NotDefined(Exception):
|
||||
def __init__(self, app, name):
|
||||
self.app = app
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
|
||||
self.name)
|
||||
|
||||
def register(app, name, type="string", deflt=None, help=None, **kwargs):
|
||||
"""Register a new parameter.
|
||||
|
||||
app corresponds to a core component (admin, main) or an extension.
|
||||
"""
|
||||
if not app in _params.keys():
|
||||
_params[app] = {}
|
||||
_params[app][name] = {"type" : type, "default" : deflt, "help" : help}
|
||||
|
||||
def save(app, name, value):
|
||||
if not app in _params.keys() or not name in _params[app].keys():
|
||||
raise NotDefined(app, name)
|
||||
fullname = "%s.%s" % (app, name)
|
||||
try:
|
||||
p = Parameter.objects.get(name=fullname)
|
||||
except Parameter.DoesNotExist:
|
||||
p = Parameter()
|
||||
p.name = fullname
|
||||
if p.value != value:
|
||||
p.value = value
|
||||
p.save()
|
||||
return True
|
||||
|
||||
def get(app, name):
|
||||
if not app in _params.keys() or not name in _params[app].keys():
|
||||
raise NotDefined(app, name)
|
||||
try:
|
||||
p = Parameter.objects.get(name="%s.%s" % (app, name))
|
||||
except Parameter.DoesNotExist:
|
||||
return _params[app][name]["default"]
|
||||
return p.value
|
||||
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import inspect
|
||||
import lxml
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
class Column:
|
||||
sortable = True
|
||||
|
||||
def __init__(self, name, **kwargs):
|
||||
self.name = name
|
||||
for k, v in kwargs.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
label = getattr(self, "label")
|
||||
except KeyError:
|
||||
label = getattr(self, "name")
|
||||
return label
|
||||
|
||||
class SelectionColumn(Column):
|
||||
def __str__(self):
|
||||
return "<input type='checkbox' name='selectall' id='selectall' />"
|
||||
|
||||
def render(self, value):
|
||||
return "<input type='checkbox' name='%s' value='%s' />" \
|
||||
% (self.name, value)
|
||||
|
||||
class ImgColumn(Column):
|
||||
sortable = False
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
return getattr(self, "header")
|
||||
except AttributeError:
|
||||
return ""
|
||||
|
||||
def render(self, value):
|
||||
return "<img src='%s' />" % value
|
||||
|
||||
class Table(object):
|
||||
tableid = ""
|
||||
|
||||
def __init__(self, rows={}):
|
||||
self.columns = []
|
||||
for m in inspect.getmembers(self):
|
||||
if isinstance(m[1], Column):
|
||||
try:
|
||||
if getattr(m[1], "first"):
|
||||
self.columns = [m[1]] + self.columns
|
||||
except AttributeError:
|
||||
self.columns += [m[1]]
|
||||
self.rows = []
|
||||
for row in rows:
|
||||
nrow = {"id" : row[self.idkey], "cols" : []}
|
||||
for c in self.columns:
|
||||
newcol = {}
|
||||
try:
|
||||
newcol["width"] = getattr(c, "width")
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(c, SelectionColumn):
|
||||
newcol["value"] = c.render(row[self.idkey])
|
||||
newcol["safe"] = True
|
||||
|
||||
if isinstance(c, ImgColumn):
|
||||
key = "img_%s" % c.name
|
||||
if key in row.keys():
|
||||
newcol["value"] = c.render(row[key])
|
||||
newcol["safe"] = True
|
||||
|
||||
if row.has_key(c.name):
|
||||
try:
|
||||
cssclass = getattr(c, "cssclass")
|
||||
except AttributeError:
|
||||
cssclass = ""
|
||||
if "class" in row.keys():
|
||||
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
|
||||
newcol["value"] = self.parse(c.name, row[c.name])
|
||||
newcol["class"] = cssclass
|
||||
nrow["cols"] += [newcol]
|
||||
self.rows += [nrow]
|
||||
|
||||
def render(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_head.html" %}
|
||||
{% include "common/table_body.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : self.tableid
|
||||
}))
|
||||
|
||||
def render_head(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_head.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : "%s_head" % self.tableid
|
||||
}))
|
||||
|
||||
def render_body(self, request):
|
||||
t = Template("""
|
||||
<table id="{{ tableid }}">
|
||||
{% include "common/table_body.html" %}
|
||||
</table>
|
||||
""")
|
||||
return t.render(Context({
|
||||
"table" : self, "tableid" : "%s_body" % self.tableid
|
||||
}))
|
||||
|
||||
@@ -478,7 +478,7 @@ msgstr "Domaines"
|
||||
|
||||
#: templates/layout.html:87 templates/admin/settings_header.html:6
|
||||
msgid "Settings"
|
||||
msgstr "Paramètres"
|
||||
msgstr "Configuration"
|
||||
|
||||
#: templates/admin/addpermission.html:24 templates/admin/settings_menu.html:15
|
||||
msgid "Add permission"
|
||||
|
||||
@@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _
|
||||
class ARmessageForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ARmessage
|
||||
fields = ('subject', 'content', 'enabled')
|
||||
fields = ('subject', 'content', 'enabled', 'untildate')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ARmessageForm, self).__init__(*args, **kwargs)
|
||||
|
||||
@@ -7,6 +7,7 @@ class ARmessage(models.Model):
|
||||
subject = models.CharField(_('subject'), max_length=255)
|
||||
content = models.TextField(_('content'))
|
||||
enabled = models.BooleanField(_('enabled'))
|
||||
# untildate = models.DateTimeField(_('until'))
|
||||
|
||||
class ARhistoric(models.Model):
|
||||
armessage = models.ForeignKey(ARmessage)
|
||||
|
||||
@@ -1,8 +1,181 @@
|
||||
window.addEvent('domready', function(){
|
||||
if ($('menubar')) {
|
||||
$('menubar').getElements('li.dropdown').each( function( elem ){
|
||||
parse_menubar('topmenubar');
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});
|
||||
|
||||
|
||||
infobox = new InfoBox({
|
||||
parent : $("menubar"),
|
||||
message : "Loading"
|
||||
});
|
||||
});
|
||||
|
||||
callbacks = {};
|
||||
|
||||
register_callback = function(name, callback) {
|
||||
callbacks[name] = callback;
|
||||
}
|
||||
|
||||
get_callback = function(name) {
|
||||
return callbacks[name];
|
||||
}
|
||||
|
||||
current_anchor = null;
|
||||
|
||||
check_anchor = function() {
|
||||
if (current_anchor.serialized == location.hash
|
||||
&& !$defined(current_anchor.force)) {
|
||||
return;
|
||||
}
|
||||
delete(current_anchor.force);
|
||||
current_anchor.from_string(location.hash);
|
||||
if (!current_anchor.serialized) {
|
||||
query = current_anchor.deflocation;
|
||||
} else {
|
||||
query = current_anchor.serialized.substring(1);
|
||||
}
|
||||
infobox.show("Loading...", "gray");
|
||||
new Request.JSON({url: query, onSuccess: function(resp) {
|
||||
callback = ($defined(resp.callback)) ? resp.callback : "default";
|
||||
callbacks[callback](resp);
|
||||
infobox.notice("Done");
|
||||
infobox.hide(1);
|
||||
}}).get();
|
||||
}
|
||||
|
||||
ajaxListener = function(deflocation, defcallback) {
|
||||
current_anchor = new HashWrapper(deflocation);
|
||||
register_callback("default", defcallback);
|
||||
check_anchor.periodical(300);
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
function HashWrapper(deflocation) {
|
||||
this.deflocation = deflocation;
|
||||
this.params = $H();
|
||||
this.base = '';
|
||||
this.serialized = null;
|
||||
|
||||
this.reset = function() {
|
||||
this.base = null;
|
||||
this.params.empty();
|
||||
return this;
|
||||
};
|
||||
|
||||
this.parse_string = function(value, reset) {
|
||||
var splits = (value.charAt(0) == '#')
|
||||
? value.substring(1).split('?') : value.split('?');
|
||||
|
||||
if (splits.length == 0) {
|
||||
return this;
|
||||
}
|
||||
if (!$defined(reset)) {
|
||||
reset = false;
|
||||
}
|
||||
if (reset) {
|
||||
this.reset();
|
||||
}
|
||||
this.base = (splits[0] == "") ? this.deflocation : splits[0];
|
||||
if (this.base[this.base.length - 1] != '/') {
|
||||
this.base += '/';
|
||||
}
|
||||
if (splits.length > 1) {
|
||||
params = splits[1].split('&');
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
tmp = params[i].split('=');
|
||||
this.setparam(tmp[0], tmp[1]);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
this.from_string = function(value, reset) {
|
||||
this.parse_string(value);
|
||||
this.serialized = value;
|
||||
return this;
|
||||
};
|
||||
|
||||
this.serialize = function() {
|
||||
var res = this.base;
|
||||
|
||||
if (this.params.getLength() != 0) {
|
||||
res += "?" + this.params.toQueryString();
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
this.update = function(force) {
|
||||
location.hash = this.serialize();
|
||||
if ($defined(force)) {
|
||||
this.force = force;
|
||||
}
|
||||
};
|
||||
|
||||
this.updateparams = function(str) {
|
||||
if (str.charAt(0) == '?') {
|
||||
str = str.substring(1);
|
||||
}
|
||||
var elems = str.split('&');
|
||||
for (var i = 0; i < elems.length; i++) {
|
||||
this.setparamfromstring(elems[i]);
|
||||
}
|
||||
};
|
||||
|
||||
this.setparamfromstring = function(str) {
|
||||
var def = str.split('=');
|
||||
this.params.set(def[0], def[1]);
|
||||
};
|
||||
|
||||
this.setparam = function(name, value) {
|
||||
this.params.set(name, value);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.setparams = function(params) {
|
||||
this.params.extend(params);
|
||||
return this;
|
||||
};
|
||||
|
||||
this.getparam = function(name) {
|
||||
if (!this.params.has(name)) {
|
||||
return "";
|
||||
}
|
||||
return this.params.get(name);
|
||||
}
|
||||
|
||||
this.baseurl = function(value) {
|
||||
this.reset();
|
||||
this.base = value;
|
||||
if (this.base[this.base.length - 1] != '/') {
|
||||
this.base += '/';
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
this.delparam = function(name) {
|
||||
if (!this.params.has(name)) {
|
||||
return false;
|
||||
}
|
||||
this.params.erase(name);
|
||||
return this;
|
||||
};
|
||||
};
|
||||
|
||||
parse_menubar = function(id) {
|
||||
if ($(id)) {
|
||||
$(id).getElements('li.dropdown').each(function(elem){
|
||||
var list = elem.getElement('ul.links');
|
||||
var myFx = new Fx.Slide(list).hide();
|
||||
var myFx = new Fx.Slide(list, {
|
||||
duration: 200
|
||||
}).hide();
|
||||
|
||||
myFx.addEvent("start", function() {
|
||||
this.wrapper.setStyle("clear", "both");
|
||||
});
|
||||
|
||||
elem.addEvents({
|
||||
'mouseenter' : function(){
|
||||
myFx.cancel();
|
||||
@@ -14,11 +187,11 @@ window.addEvent('domready', function(){
|
||||
}
|
||||
});
|
||||
});
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});
|
||||
}
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_iframe_body = function(id) {
|
||||
var saf = navigator.userAgent.match(/Safari/i);
|
||||
@@ -65,3 +238,42 @@ confirmation = function(question, action, callback) {
|
||||
});
|
||||
SqueezeBox.open("/mailng/main/confirm/?question=" + question);
|
||||
}
|
||||
|
||||
setDivHeight = function(id, extrah, modulo) {
|
||||
var contentsize = $(document.body).getSize().y
|
||||
- $("topmenubar").getSize().y
|
||||
- $("header").getSize().y
|
||||
- $("footer").getSize().y
|
||||
- extrah;
|
||||
|
||||
if (modulo) {
|
||||
contentsize -= contentsize % modulo;
|
||||
}
|
||||
$(id).setStyle("height", contentsize + "px");
|
||||
}
|
||||
|
||||
bindRows = function(rooturl, page) {
|
||||
$$("tr").addEvent("click", function(event) {
|
||||
if (event.target.parentNode.id) {
|
||||
var url = rooturl + event.target.parentNode.id + "?page=" + page;
|
||||
event.target.parentNode.setStyle("background", "#ffffcc");
|
||||
location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleSelection = function(id, value) {
|
||||
$$("input[name=" + id + "]").each(function(obj) {
|
||||
obj.checked = value;
|
||||
});
|
||||
}
|
||||
|
||||
checkSelection = function(id, text) {
|
||||
var getout = $$("input[name=" + id + "]").every(function(item, index) {
|
||||
return item.checked == false;
|
||||
});
|
||||
if (getout) {
|
||||
return false;
|
||||
}
|
||||
return confirm(text);
|
||||
}
|
||||
@@ -40,7 +40,7 @@ window.addEvent('domready', function() {
|
||||
});
|
||||
|
||||
|
||||
$('body').addEvent('click', function(event) {
|
||||
$(document.body).addEvent('click', function(event) {
|
||||
target = event.target;
|
||||
if (target.get('tag') == 'a') {
|
||||
name = target.get('name');
|
||||
@@ -54,7 +54,7 @@ window.addEvent('domready', function() {
|
||||
}
|
||||
});
|
||||
|
||||
$('body').addEvent('submit', function(event) {
|
||||
$(document.body).addEvent('submit', function(event) {
|
||||
target = event.target;
|
||||
if (target.get('tag') == 'form') {
|
||||
name = target.get('name');
|
||||
@@ -66,6 +66,7 @@ window.addEvent('domready', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
setDivHeight("content", 20, 0);
|
||||
});
|
||||
|
||||
newdom = function() {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
var InfoBox = new Class({
|
||||
Implements: [Options, Events],
|
||||
options: {
|
||||
parent : null,
|
||||
message : "This is my info box!",
|
||||
width : 200,
|
||||
},
|
||||
|
||||
profiles: {
|
||||
gray : {"background" : "#EEEEEE", "border" : "#CCCCCC"},
|
||||
green : {"background" : "#DAF8DA", "border" : "#166D14"},
|
||||
red : {"background" : "#F9D2D2", "border" : "#FF2A00"},
|
||||
yellow : {"background" : "#FDE6B5", "border" : "#FF7F00"}
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.setOptions(options);
|
||||
|
||||
var boxleft = document.body.getSize().x / 2 - this.options.width / 2;
|
||||
this.box = new Element("div", {"id" : "infobox"});
|
||||
this.box.setStyles({
|
||||
"font-size" : "normal",
|
||||
"font-weight" : "bold",
|
||||
"text-align" : "left",
|
||||
"width" : this.options. width + "px",
|
||||
"padding" : "10px",
|
||||
"position" : "absolute",
|
||||
"top" : "0px",
|
||||
"left" : boxleft + "px",
|
||||
"display" : "none"
|
||||
});
|
||||
this.setProfile("gray");
|
||||
this.spinner = new Element("img", {
|
||||
"src" : "/static/stylesheets/spinner.gif"
|
||||
});
|
||||
this.spinner.setStyle("float", "left");
|
||||
this.spinner.inject(this.box);
|
||||
|
||||
this.label = new Element("label").set("html", this.options.message);
|
||||
this.label.setStyles({
|
||||
"margin-left" : "10px"
|
||||
});
|
||||
this.label.inject(this.box);
|
||||
this.box.inject(document.body);
|
||||
},
|
||||
|
||||
setMessage: function(text) {
|
||||
this.label.set("html", text);
|
||||
},
|
||||
|
||||
setProfile: function(color) {
|
||||
this.box.setStyles({
|
||||
"background" : this.profiles[color].background,
|
||||
"border" : "2px solid " + this.profiles[color].border
|
||||
});
|
||||
},
|
||||
|
||||
notice: function(message) {
|
||||
this.setProfile("green");
|
||||
this.setMessage(message);
|
||||
},
|
||||
|
||||
error: function(message) {
|
||||
this.setProfile("red");
|
||||
this.setMessage(message);
|
||||
},
|
||||
|
||||
warning: function(message) {
|
||||
this.setProfile("yellow");
|
||||
this.setMessage(message);
|
||||
},
|
||||
|
||||
show: function(message, prof, delay) {
|
||||
if ($defined(this.start)) {
|
||||
if ($time() - this.start > this.delay) {
|
||||
$clear(this.showtimer);
|
||||
delete(this.start);
|
||||
delete(this.delay);
|
||||
} else {
|
||||
this.showtimer = this.show.delay(100, this, [message, prof]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($defined(message)) {
|
||||
this.setMessage(message);
|
||||
}
|
||||
if ($defined(prof)) {
|
||||
this.setProfile(prof);
|
||||
}
|
||||
if ($defined(delay)) {
|
||||
this.start = $time();
|
||||
this.delay = delay * 1000;
|
||||
}
|
||||
this.box.setStyle("display", "block");
|
||||
},
|
||||
|
||||
hide: function(timer) {
|
||||
if ($defined(timer)) {
|
||||
this.timer = this.hide.periodical(timer * 1000, this);
|
||||
return;
|
||||
}
|
||||
$clear(this.timer);
|
||||
this.box.setStyle("display", "none");
|
||||
},
|
||||
});
|
||||
@@ -2,4 +2,9 @@ window.addEvent('domready', function() {
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel'
|
||||
});
|
||||
|
||||
new DatePicker("#untildate", {
|
||||
pickerClass: "datepicker_vista",
|
||||
allowEmpty: true
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,25 @@ window.addEvent('domready', function() {
|
||||
parse: 'rel',
|
||||
|
||||
});
|
||||
setDivHeight("content", 20, 0);
|
||||
|
||||
if ($$("a[name=deletePerm]"))
|
||||
$$("a[name=deletePerm]").addEvent('click', function(event) {
|
||||
if (!confirm(gettext("Remove this permission?")))
|
||||
event.stop();
|
||||
});
|
||||
|
||||
var tip = new Tips($$(".Tips"));
|
||||
|
||||
var accordion = new Accordion('h3.atStart', 'div.atStart', {
|
||||
opacity: false,
|
||||
onActive: function(toggler, element){
|
||||
toggler.setStyle('color', '#ff3300');
|
||||
},
|
||||
|
||||
onBackground: function(toggler, element){
|
||||
toggler.setStyle('color', '#222');
|
||||
}
|
||||
}, $('accordion'));
|
||||
|
||||
});
|
||||
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 773 B |
|
After Width: | Height: | Size: 580 B |
|
After Width: | Height: | Size: 498 B |
|
After Width: | Height: | Size: 528 B |
|
After Width: | Height: | Size: 868 B |
|
After Width: | Height: | Size: 693 B |
|
After Width: | Height: | Size: 540 B |
|
Before Width: | Height: | Size: 611 B After Width: | Height: | Size: 629 B |
|
After Width: | Height: | Size: 595 B |
|
After Width: | Height: | Size: 550 B |
@@ -2,6 +2,7 @@ body {
|
||||
font-family: arial, helvetica, verdana, tahoma, sans-serif;
|
||||
font-size: small;
|
||||
background: #153F65;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#first-container {
|
||||
@@ -342,3 +343,13 @@ body {
|
||||
margin: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* nouveau style */
|
||||
#topmenubar {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
background: #072342;
|
||||
border-bottom: 2px solid #333;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ input:hover {
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
|
||||
td.odd {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
|
||||
td.even {
|
||||
|
||||
}
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
width: 100%;
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Document : calendar.css
|
||||
Created on : 29.07.2009
|
||||
Author : jens klose
|
||||
Description:
|
||||
example styles for calendar
|
||||
*/
|
||||
.calendarForm{
|
||||
float:right;
|
||||
}
|
||||
.calendar{
|
||||
font-family:verdana,sans-serif;
|
||||
font-size:10px;
|
||||
float:None;
|
||||
}
|
||||
button.calendar{
|
||||
position:relative;
|
||||
top:-3px;
|
||||
left:1px;
|
||||
background-color:transparent;
|
||||
background-image:url(../pics/calendar.gif);
|
||||
border:none;
|
||||
margin: 0px;
|
||||
padding:0px;
|
||||
margin-right:3px;
|
||||
cursor:pointer;
|
||||
width:16px;
|
||||
height:16px;
|
||||
}
|
||||
input.calendar{
|
||||
cursor:pointer;
|
||||
border: 1px solid #87847F;
|
||||
height:12px;
|
||||
width:100px;
|
||||
color: #87847F;
|
||||
}
|
||||
div.calendar{
|
||||
background:white;
|
||||
border: 1px solid #87847F;
|
||||
width:180px
|
||||
}
|
||||
a.footer{
|
||||
color:#474747;
|
||||
padding:2px;
|
||||
}
|
||||
.calendar table{
|
||||
width:100%;
|
||||
}
|
||||
.calendar th,
|
||||
.calendar td{
|
||||
text-align:center;
|
||||
font-size:10px;
|
||||
padding: 0.1em 0.25em 0.1em 0.3em;
|
||||
}
|
||||
.calendar tbody td,
|
||||
.calendar .invalid{
|
||||
color:silver;
|
||||
}
|
||||
.calendar .valid{
|
||||
color:blue;
|
||||
font-weight:bold;
|
||||
cursor:pointer;
|
||||
}
|
||||
.calendar .valid.hover{
|
||||
background:#FFFF77;
|
||||
font-weight:bold;
|
||||
}
|
||||
.calendar caption a,
|
||||
.calendar .next{
|
||||
color:blue;
|
||||
font-weight:bold;
|
||||
padding:2px;
|
||||
cursor:pointer;
|
||||
}
|
||||
.calendar caption a,
|
||||
.calendar .prev{
|
||||
color:blue;
|
||||
font-weight:bold;
|
||||
padding:2px;
|
||||
}
|
||||
.calendar .active,
|
||||
.calendar .hilite,
|
||||
.calendar .inactive{
|
||||
color:white;
|
||||
background-color:#BE3916;
|
||||
font-weight:bold;
|
||||
}
|
||||
.calendar .active,
|
||||
.calendar .inactive{
|
||||
background-color:red;
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
html, body {
|
||||
font-family: arial, helvetica, verdana, tahoma, sans-serif;
|
||||
font-size: small;
|
||||
background: #153F65;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div#container {
|
||||
height: auto !important;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
@media screen {
|
||||
body > div#footer {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
|
||||
div#topmenubar {
|
||||
position: fixed;
|
||||
border-bottom: 5px solid #333;
|
||||
height: 30px;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
div#footer {
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
bottom: 0px;
|
||||
border-top: 5px solid #333;
|
||||
}
|
||||
|
||||
#menubar {
|
||||
margin: 5px;
|
||||
display: table;
|
||||
width: 700px;
|
||||
margin-top: 35px;
|
||||
/*position: absolute;*/
|
||||
}
|
||||
|
||||
#topmenubar a, #topmenubar a:visited {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#topmenubar label {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#menubar a, #menubar a:visited {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#menubar label {
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#page {
|
||||
width: 100%;
|
||||
top: 35px;
|
||||
bottom: 35px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#content {
|
||||
position: fixed;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
top: 130px;
|
||||
bottom: 35px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#header {
|
||||
width: 100%;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
font-size: medium;
|
||||
width: 50%;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
#loginbox {
|
||||
margin: 100px auto;
|
||||
width: 610px;
|
||||
padding-bottom: 15px;
|
||||
border: 2px solid #333;
|
||||
background: white;
|
||||
}
|
||||
|
||||
#loginbox h2 {
|
||||
margin: 30px 10px 0 10px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
#loginbox h3 {
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#logo {
|
||||
float: left;
|
||||
text-align: center;
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
#test {
|
||||
float: right;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.uibar {
|
||||
background: #153F65;
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menubar a, .menubar a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menubar a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.menubar ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
width: 100%;
|
||||
color: #2a4069;
|
||||
}
|
||||
|
||||
.menubar li {
|
||||
float: left;
|
||||
display: inline;
|
||||
margin: 5px 5px;
|
||||
}
|
||||
|
||||
.menubar li.selected {
|
||||
background: #ffff77;
|
||||
}
|
||||
|
||||
.menubar img {
|
||||
float: left;
|
||||
margin: 0px 3px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.menubar .title {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.menubardropdown ul {
|
||||
background: white;
|
||||
width: 120px;
|
||||
border-bottom: 5px solid #ccc;
|
||||
border-right: 5px solid #ccc;
|
||||
border-left: 5px solid #ccc;
|
||||
}
|
||||
|
||||
div.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
div.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: right;
|
||||
/*margin: 5px 0 0 5px;*/
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
clear: both;
|
||||
z-index: 2;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.dropdown li {
|
||||
padding: 5px;
|
||||
width: 170px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.topdropdown ul {
|
||||
background: #153F65;
|
||||
border-bottom: 5px solid #333;
|
||||
border-right: 5px solid #333;
|
||||
border-left: 5px solid #333;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.dropdown ul li {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Mootools tooltips */
|
||||
.tip {
|
||||
color: #fff;
|
||||
width: 139px;
|
||||
z-index: 13000;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
color: #9FD4FF;
|
||||
padding: 8px 8px 4px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px 8px;
|
||||
background: #eee;
|
||||
color: black;
|
||||
}
|
||||
|
||||
table {
|
||||
/*margin: 0 auto;*/
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
|
||||
tr.odd {
|
||||
background: white;
|
||||
}
|
||||
|
||||
tr.even {
|
||||
background: #F4F6F8;
|
||||
}
|
||||
|
||||
th, td {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #CEDBEC;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
th.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: 1px solid #C8D6E8;
|
||||
/*padding: 5px;*/
|
||||
}
|
||||
|
||||
input {
|
||||
border: 1px solid #cccccc;
|
||||
color: inherit;
|
||||
/*background-color: #e9e9e9;*/
|
||||
background-color: #eee;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
input:hover {
|
||||
color: inherit;
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
|
||||
table.hr {
|
||||
width: 80%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.hr td.odd {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
|
||||
.hr td.even {
|
||||
|
||||
}
|
||||
|
||||
.unseen {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.toggler {
|
||||
margin: 5px auto;
|
||||
padding: 2px 5px;
|
||||
background: #CEDBEC;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
/*border: 1px solid #CCC;*/
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.element {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
div.row {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
div.row label {
|
||||
float: left;
|
||||
width: 200px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin: 0 auto;
|
||||
padding: 5px;
|
||||
background: red;
|
||||
width: 500px;
|
||||
text-align: center;
|
||||
background-color: #F9D2D2;
|
||||
border: 3px solid #FF2A2A;
|
||||
color: #FF2A00;
|
||||
}
|
||||
|
||||
.errorlist ul {
|
||||
float: left;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.errorlist li {
|
||||
margin-left: 10px;
|
||||
float: left;
|
||||
display: inline;
|
||||
color: red;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
html, body {
|
||||
font-family: arial, helvetica, verdana, tahoma, sans-serif;
|
||||
font-size: small;
|
||||
background: #153F65;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#loginbox {
|
||||
margin: 100px auto;
|
||||
width: 610px;
|
||||
padding-bottom: 15px;
|
||||
background: white;
|
||||
|
||||
border: 5px solid #333;
|
||||
/*-moz-box-shadow: 0 10px 30px #222;*/
|
||||
-webkit-box-shadow: 0 10px 30px #222;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
#loginbox h2 {
|
||||
margin: 30px 10px 0 10px;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
#loginbox h3 {
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#logo {
|
||||
float: left;
|
||||
text-align: center;
|
||||
width: 300px;
|
||||
margin-top: 10px;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
#logo img {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
#test {
|
||||
float: right;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Mootools tooltips */
|
||||
.tip {
|
||||
color: #fff;
|
||||
width: 139px;
|
||||
z-index: 13000;
|
||||
border: 1px solid #cccccc;
|
||||
}
|
||||
|
||||
.tip-title {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin: 0;
|
||||
color: #9FD4FF;
|
||||
padding: 8px 8px 4px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px 8px;
|
||||
background: #eee;
|
||||
color: black;
|
||||
}
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -26,7 +26,7 @@ window.addEvent("domready", function() {
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.addpermission as addperm_url %}
|
||||
<form action="{{ addperm_url }}" method="POST" id="permform" name="permform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/domains.js" type="text/javascript"></script>
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% load admin_extras %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/domains.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
window.addEvent('domready', function() {
|
||||
{% url admin.views.domains as view_url %}
|
||||
bindRows("{{ view_url }}", "{{ current_page }}");
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Available domains" %}{% endblock %}
|
||||
{% block menu %}{% domains_menu "domains" perms %}{% endblock %}
|
||||
{% block content %}
|
||||
<div id="domcontent">
|
||||
{% if not domains %}
|
||||
@@ -12,13 +20,13 @@
|
||||
<table>
|
||||
<tr>
|
||||
<th width="50px">{% trans "Actions" %}</th>
|
||||
<th width="80px">{% trans "Name" %}</th>
|
||||
<th width="50px">{% trans "Mailboxes" %}</th>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th width="150px">{% trans "Mailboxes" %}</th>
|
||||
<th width="50px">{% trans "Quota" %}</th>
|
||||
<th width="50px">{% trans "Enabled" %}</th>
|
||||
</tr>
|
||||
{% for dom in domains %}
|
||||
<tr class="{% cycle 'odd' 'even' %}">
|
||||
<tr class="{% cycle 'odd' 'even' %}" id="{{ dom.id }}">
|
||||
<td>
|
||||
{% url admin.views.editdomain dom.id as edit_url %}
|
||||
<a href="{{ edit_url }}" class="boxed"
|
||||
@@ -31,7 +39,7 @@
|
||||
</a>
|
||||
</td>
|
||||
{% url admin.views.mailboxes dom.id as domurl %}
|
||||
<td><a name="loadDom" href="{{ domurl }}">{{ dom.name }}</a></td>
|
||||
<td>{{ dom.name }}</td>
|
||||
<td>{{ dom.mboxcounter }}</td>
|
||||
<td>{{ dom.quota }} {% trans "MB" %}</td>
|
||||
<td>{{ dom.enabled|yesno:_("yes,no") }}</td>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.editalias alias.mbox.domain.id alias.id as edit_url %}
|
||||
<form action="{{ edit_url }}" method="POST" id="aliasform" name="aliasform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" name="submit" value="{% trans 'Update' %}" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.editdomain domain.id as dom_url %}
|
||||
<form action="{{ dom_url }}" method="post" name="domform" id="domform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" class="button" name="submit" value="{% trans 'Update' %}" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %}
|
||||
<form action="{{ box_url }}" method="POST" id="mboxform" name="mboxform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% block content %}
|
||||
{% load i18n %}
|
||||
<div id="body">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.newalias mbox.domain_id mbox.id as box_url %}
|
||||
<form action="{{ box_url }}" method="POST" id="aliasform" name="aliasform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" name="submit" value="{% trans 'Create' %}" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.newdomain as dom_url %}
|
||||
<form action="{{ dom_url }}" method="post" name="domform" id="domform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" class="button" name="submit" value="{% trans 'Create' %}" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url admin.views.newmailbox domain.id as box_url %}
|
||||
<form action="{{ box_url }}" method="POST" id="mboxform" name="mboxform">
|
||||
{% include "admin/genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% extends "admin/settings_header.html" %}
|
||||
{% block menu %}
|
||||
{% load admin_extras %}
|
||||
{% settings_menu "parameters" perms %}
|
||||
{% endblock %}
|
||||
{% block setcontent %}
|
||||
{% load i18n %}
|
||||
{% url admin.views.saveparameters as action %}
|
||||
<form method="POST" action="{{ action }}">
|
||||
<div id="accordion">
|
||||
{% for app in gparams %}
|
||||
<h3 class="toggler atStart">{{ app.name }}</h3>
|
||||
<div class="element atStart">
|
||||
{% for p in app.params %}
|
||||
{% param app.name p %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<p><input type="submit" name="update" value="{% trans 'Save' %}" /></p>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,7 @@
|
||||
{% extends "admin/settings_header.html" %}
|
||||
{% block menu %}
|
||||
{% load admin_extras %}
|
||||
{% settings_menu "permissions" %}
|
||||
{% settings_menu "permissions" perms %}
|
||||
{% endblock %}
|
||||
{% block setcontent %}
|
||||
{% load i18n %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/settings.js" type="text/javascript"></script>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{% load i18n %}
|
||||
<ul>
|
||||
{% url admin.views.permissions as perm_url %}
|
||||
<li {% ifequal selection "permissions" %}class="selected"{% endifequal %}>
|
||||
<a href="{{ perm_url }}">
|
||||
<img src="/static/pics/permissions.png" border="0" />
|
||||
{% trans "Permissions" %}
|
||||
</a>
|
||||
</li>
|
||||
{% url admin.views.addpermission as addperm_url %}
|
||||
<li>
|
||||
<a href="{{ addperm_url }}" class="boxed"
|
||||
rel="{handler:'iframe',size:{x:320,y:210}}">
|
||||
<img src="/static/pics/add.png" border="0" />
|
||||
{% trans "Add permission" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -0,0 +1,79 @@
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<style type="text/css">
|
||||
#navbar {
|
||||
float: right;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#navbar label {
|
||||
color: white;
|
||||
margin-right: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#navbar img{
|
||||
margin-top: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
{% block css %}{% endblock %}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
window.addEvent("domready", function() {
|
||||
navparams = new Hash({
|
||||
{% for k, v in navparams.items %}
|
||||
{{ k }} : "{{ v }}",
|
||||
{% endfor %}
|
||||
});
|
||||
|
||||
navbarcallback = function(event) {
|
||||
event.stop();
|
||||
current_anchor.setparams(navparams);
|
||||
current_anchor.parse_string(event.target.getParent().get("href")).update();
|
||||
}
|
||||
|
||||
viewmail = function(event) {
|
||||
event.stop();
|
||||
if (event.target.parentNode.id) {
|
||||
var url = event.target.parentNode.id + "/";
|
||||
event.target.parentNode.setStyle("background", "#ffffcc");
|
||||
current_anchor.baseurl(url).update();
|
||||
}
|
||||
}
|
||||
|
||||
updatelisting = function(resp) {
|
||||
if ($defined(resp.menu)) {
|
||||
$("menubar").set("html", resp.menu);
|
||||
parse_menubar("menubar");
|
||||
}
|
||||
$("listing").set("html", resp.listing);
|
||||
if ($defined(resp.navbar)) {
|
||||
$("navbar").set("html", resp.navbar);
|
||||
$("navbar").getChildren("a").addEvent("click", navbarcallback);
|
||||
}
|
||||
}
|
||||
|
||||
if ($("selectall")) {
|
||||
$("selectall").addEvent("click", function(event) {
|
||||
if ($("selectall").checked) {
|
||||
toggleSelection("selection", true);
|
||||
} else {
|
||||
toggleSelection("selection", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
{% block js %}{% endblock %}
|
||||
ajaxListener("{{ deflocation }}", {{ defcallback }});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% block leftcol %}{% endblock %}
|
||||
<div id="listing">{{ listing }}</div>
|
||||
<div class="spacer"></div>
|
||||
{% endblock %}
|
||||
{% block footer %}<div id="navbar">{{ navbar }}</div>{% endblock %}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
<div id="searchbox">
|
||||
<img src="/static/pics/search.png" id="searchopts" />
|
||||
<input type="text" name="searchfield" id="searchfield" value="{% trans "Search..." %}" />
|
||||
<div id="searchparams">
|
||||
<input type="radio" name="scriteria" value="from_addr" checked>{% trans "From address" %}<br/>
|
||||
<input type="radio" name="scriteria" value="subject">{% trans "Subject" %}<br/>
|
||||
<input type="radio" name="scriteria" value="from_addr,subject">{% trans "Both" %}<br/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block middletitle %}{% trans "Error" %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="error"><p>{{ error }}</p></div>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,8 @@
|
||||
{% for field in form %}
|
||||
<div class="row">
|
||||
{% if field.help_text %}
|
||||
<div id="{{ field.name }}_helptext" style="display:none">{{ field.help_text}}</div>{% endif %}
|
||||
<div id="{{ field.name }}_helptext" style="display:none">{{ field.help_text}}</div>
|
||||
{% endif %}
|
||||
{{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -0,0 +1 @@
|
||||
{% if pre %}<pre>{% endif %}{{ body }}{% if pre %}</pre>{% endif %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% load i18n %}
|
||||
<label>Messages {{ page.id_start }} {% trans "to" %} {{ page.id_stop }} {% trans "of" %} {{ page.items }}</label>
|
||||
|
||||
{% if page.previous_page %}
|
||||
<a href="{{ page.baseurl }}?page=1">
|
||||
<img src="/static/pics/first.png" border="0" title="{% trans "First" %}" />
|
||||
</a>
|
||||
<a href="{{ page.baseurl }}?page={{ page.previous_page }}">
|
||||
<img src="/static/pics/back.png" border="0" title="{% trans "Previous" %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if page.next_page %}
|
||||
<a href="{{ page.baseurl }}?page={{ page.next_page }}">
|
||||
<img src="/static/pics/next.png" border="0" title="{% trans "Next" %}" />
|
||||
</a>
|
||||
<a href="{{ page.baseurl }}?page={{ page.last_page }}">
|
||||
<img src="/static/pics/last.png" border="0" title="{% trans "Last" %}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -0,0 +1,13 @@
|
||||
<tbody>
|
||||
{% for row in table.rows %}
|
||||
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
|
||||
{% for cell in row.cols %}
|
||||
{% if cell.safe %}
|
||||
<td width="{{ cell.width }}">{{ cell.value|safe }}</td>
|
||||
{% else %}
|
||||
<td width="{{ cell.width }}" class="{{ cell.class }}">{{ cell.value }}</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@@ -0,0 +1,14 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{% for c in table.columns %}
|
||||
{% if c.sortable %}
|
||||
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
|
||||
{% else %}
|
||||
<th id="{{ c.name }}" width="{{ c.width }}">
|
||||
{% endif %}
|
||||
{{ c|safe }}
|
||||
<span style="float: right"><img src=""/></span>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -0,0 +1,26 @@
|
||||
<table id="{{ tableid }}">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for c in table.columns %}
|
||||
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
|
||||
{{ c|safe }}
|
||||
<span style="float: right"><img src="" /></span>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for row in table.rows %}
|
||||
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
|
||||
{% for cell in row.cols %}
|
||||
{% if cell.safe %}
|
||||
<td width="{{ c.width }}">{{ cell.value|safe }}</td>
|
||||
{% else %}
|
||||
<td width="{{ c.width }}" class="{{ cell.class }}">{{ cell.value }}</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -0,0 +1,54 @@
|
||||
{% load i18n %}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head profile="http://www.w3.org/2005/10/profile">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 5px;
|
||||
}
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
border: 0px transparent none;
|
||||
margin: 0;
|
||||
}
|
||||
table#emailheaders {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#emailheaders td,
|
||||
#emailheaders th {
|
||||
height: 22px;
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
#emailheaders td {
|
||||
font-size: 1em;
|
||||
}
|
||||
#emailheaders th {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
#emailheaders a, #emailheaders a:visited {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
#emailheaders a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
pre {
|
||||
font-size: medium;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="table-container">
|
||||
{{ headers }}
|
||||
</div>
|
||||
{% if pre %}<pre>{% endif %}{{ mailbody }}{% if pre %}</pre>{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,5 +0,0 @@
|
||||
{% for field in form %}
|
||||
<div class="row">
|
||||
{{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -1,8 +1,10 @@
|
||||
{% extends "main/header.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/main.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/datepicker/vista.css" />
|
||||
<script src="/static/js/datepicker.js" type="text/javascript"></script>
|
||||
<script src="/static/js/main.js" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
{% block menu %}
|
||||
{% load main_extras %}
|
||||
@@ -10,11 +12,11 @@
|
||||
{% endblock %}
|
||||
{% block subcontent %}
|
||||
<form action="" method="POST" id="armform" name="armform" class="simple">
|
||||
{% include "genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
<div class="spacer"></div>
|
||||
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" name="submit" value="{% trans 'Update' %}" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
{% if error %}<div class="error">{{ error }}</div>{% endif %}
|
||||
{% url main.views.changepassword as action_url %}
|
||||
<form action="{{ action_url }}" method="post" name="chpwdform" id="chpwdform">
|
||||
{% include "genericform.html" %}
|
||||
{% include "common/genericform.html" %}
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" class="button" name="submit" value="{% trans 'Update' %}" />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/calendar.js" type="text/javascript"></script>
|
||||
<script src="/static/js/main.js" type="text/javascript"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "General" %}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<ul>
|
||||
{% for entry in entries %}
|
||||
{% if entry.menu %}
|
||||
<li class="dropdown">
|
||||
<li class="dropdown {{ entry.class }}">
|
||||
<img src="{{ entry.img }}" border="0" />
|
||||
{{ entry.label }}
|
||||
<label>{{ entry.label }}</label>
|
||||
<ul class="links">
|
||||
{% for sentry in entry.menu %}
|
||||
<a href="{{ sentry.url }}" name="{{ sentry.name }}">
|
||||
<li>{{ sentry.label }}</li>
|
||||
</a>
|
||||
<li>
|
||||
<a href="{{ sentry.url }}" name="{{ sentry.name }}">{{ sentry.label }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
@@ -26,4 +26,4 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
{% load i18n %}
|
||||
{% load main_extras %}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head profile="http://www.w3.org/2005/10/profile">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>MailNG - {%block pagetitle %}{% endblock %}</title>
|
||||
<link rel="icon" type="image/png" href="/static/stylesheets/logo-icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/default.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/SqueezeBox.css" />
|
||||
<script src="/static/js/mootools.js" type="text/javascript"></script>
|
||||
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
|
||||
<script src="/static/js/drag.group.js" type="text/javascript"></script>
|
||||
<script src="/static/js/SqueezeBox.js" type="text/javascript"></script>
|
||||
<script src="/static/js/infobox.js" type="text/javascript"></script>
|
||||
<script src="/static/js/common.js" type="text/javascript"></script>
|
||||
{% url django.views.i18n.javascript_catalog as catalog_url %}
|
||||
<script type="text/javascript" src="{{ catalog_url }}"></script>
|
||||
{%block html_head %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="topmenubar" class="uibar menubar">
|
||||
<div class="left" style="width: 50%">
|
||||
<ul>
|
||||
<li>
|
||||
{% url main.views.index as index_url %}
|
||||
<a href="{{ index_url }}">
|
||||
<img src="/static/pics/user.png" border="0" />{% trans "Preferences" %}</a>
|
||||
</li>
|
||||
{% loadextmenu perms %}
|
||||
{% if user.is_superuser or perms.admin.view_domains %}
|
||||
<li class="dropdown topdropdown">
|
||||
<img src="/static/pics/admin.png" border="0" />
|
||||
<label>{% trans "Admin" %}</label>
|
||||
<ul class="links">
|
||||
<li>
|
||||
<a href="{% url admin.views.domains %}">
|
||||
<img src="/static/pics/domains.png" />{% trans "Domains" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url admin.views.settings %}">
|
||||
<img src="/static/pics/settings.png" border="0"/>
|
||||
{% trans "Settings" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="right">
|
||||
<ul>
|
||||
<li class="title">
|
||||
{% if user.first_name %}
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
{% else %}
|
||||
{{ user.username }}
|
||||
{% endif %}
|
||||
/
|
||||
</li>
|
||||
|
||||
<li>
|
||||
{% url django.contrib.auth.views.logout_then_login as logout_url %}
|
||||
<a href="{{ logout_url }}">
|
||||
<img src="/static/pics/logout.png" border="0" />{% trans "Logout" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="page">
|
||||
<div id="header">
|
||||
<img class="logo" src="/static/stylesheets/mailng.png" />
|
||||
<h1>{% block middletitle %}{% endblock %}</h1>
|
||||
<div id="menubar" class="menubar">
|
||||
{% block menu %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="uibar">
|
||||
|
||||
{% block footer %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +1,40 @@
|
||||
{% extends "layout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div id="loginbox">
|
||||
<h3>{% trans "Welcome" %}</h3>
|
||||
{% if form.errors %}
|
||||
<div class="error">
|
||||
<p>{% trans "Your username and password didn't match. Please try again." %}</p>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head profile="http://www.w3.org/2005/10/profile">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<title>{% trans "Welcome to MailNG" %}</title>
|
||||
<link rel="icon" type="image/png" href="/static/stylesheets/logo-icon.png" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/login.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
|
||||
<script src="/static/js/mootools.js" type="text/javascript"></script>
|
||||
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
|
||||
{% url django.views.i18n.javascript_catalog as catalog_url %}
|
||||
<script type="text/javascript" src="{{ catalog_url }}"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="loginbox">
|
||||
<div id="logo">
|
||||
<img src="/static/stylesheets/mailng.png" />
|
||||
{% if error %}
|
||||
<div class="error"><p>{{ error }}</p></div>
|
||||
{% endif %}
|
||||
<h2>2010 - Version 0.7-dev</h2>
|
||||
</div>
|
||||
<div id="test">
|
||||
<h3>{% trans "Welcome" %}</h3>
|
||||
<form method="post" action="">
|
||||
{% include "common/genericform.html" %}
|
||||
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" value="login" />
|
||||
</div>
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action=".">
|
||||
{% for field in form %}
|
||||
<div class="row">
|
||||
{{ field.label_tag }}{{ field }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="lastrow" align="center">
|
||||
<input type="submit" value="login" />
|
||||
</div>
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,7 +21,7 @@ urlpatterns = patterns('',
|
||||
|
||||
(r'^mailng/admin/', include('mailng.admin.urls')),
|
||||
(r'^mailng/main/', include('mailng.main.urls')),
|
||||
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
|
||||
(r'^accounts/login/$', 'mailng.auth.views.dologin'),
|
||||
(r'^accounts/logout/$', 'django.contrib.auth.views.logout_then_login'),
|
||||
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog',
|
||||
{'packages': ('mailng',),})
|
||||
@@ -33,6 +33,15 @@ if menus != ():
|
||||
if settings.DEBUG:
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/tonio/projets/mailng/static'})
|
||||
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
|
||||
{'document_root': settings.STATIC_ROOTDIR})
|
||||
)
|
||||
|
||||
# Stats
|
||||
if 'mailng.extensions.stats' in settings.INSTALLED_APPS:
|
||||
urlpatterns += patterns(
|
||||
'',
|
||||
(r'^img/stats/(?P<path>.*)/$', 'django.views.static.serve',
|
||||
{'document_root': settings.IMG_ROOTDIR})
|
||||
)
|
||||
|
||||
|
||||
|
||||