Fusion tip
@@ -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'),
|
||||
)
|
||||
|
||||
@@ -11,11 +11,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
|
||||
|
||||
@@ -49,7 +49,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:
|
||||
@@ -404,3 +404,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
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- 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):
|
||||
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, folder=None):
|
||||
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
|
||||
|
||||
def __init__(self, filter, **kwargs):
|
||||
self.mbc = SQLconnector(filter)
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
@@ -1,49 +1,11 @@
|
||||
{% 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;
|
||||
margin-right: 10px;
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
#searchbox img {
|
||||
@@ -55,99 +17,76 @@ td {
|
||||
#searchparams {
|
||||
background: white;
|
||||
padding: 5px;
|
||||
width: 150px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.addEvent("domready", function() {
|
||||
mysubmit = function(event, name) {
|
||||
event.stop();
|
||||
new Element('input', {
|
||||
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
|
||||
}).inject($("quarlistingform"), "top");
|
||||
$("quarlistingform").set("send", {
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
mysubmit = function(event, name) {
|
||||
event.stop();
|
||||
new Element('input', {
|
||||
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
|
||||
}).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;
|
||||
}
|
||||
});
|
||||
$("quarlistingform").send();
|
||||
}
|
||||
|
||||
checkSelection = function(text) {
|
||||
var getout = $$("input[name=selection]").every(function(item, index) {
|
||||
return item.checked == false;
|
||||
});
|
||||
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("{% 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");
|
||||
});
|
||||
|
||||
if ($("selectall")) {
|
||||
$("selectall").addEvent("click", function(event) {
|
||||
if ($("selectall").checked) {
|
||||
toggleSelection(true);
|
||||
} else {
|
||||
toggleSelection(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{% 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});
|
||||
});
|
||||
|
||||
$("searchfield").addEvent("click", function(event) {
|
||||
$("searchfield").select();
|
||||
});
|
||||
|
||||
$("searchfield").addEvent("blur", function(event) {
|
||||
if ($("searchfield").value == "") {
|
||||
$("searchfield").set("value", "{% trans "Search..." %}");
|
||||
response = JSON.decode(res);
|
||||
location.href = response.url;
|
||||
}
|
||||
});
|
||||
$("listingform").send();
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
$$("a[name=delete]").addEvent("click", function(event) {
|
||||
if (!checkSelection("selection", "{% trans "Delete this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "delete");
|
||||
});
|
||||
</script>
|
||||
$$("a[name=release]").addEvent("click", function(event) {
|
||||
if (!checkSelection("selection", "{% trans "Release this selection?" %}")) {
|
||||
event.stop();
|
||||
return;
|
||||
}
|
||||
mysubmit(event, "release");
|
||||
});
|
||||
|
||||
|
||||
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;
|
||||
});
|
||||
{% url extensions.amavis_quarantine.main.search as s_url %}
|
||||
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();
|
||||
});
|
||||
|
||||
$("searchfield").addEvent("blur", function(event) {
|
||||
if ($("searchfield").value == "") {
|
||||
$("searchfield").set("value", "{% trans "Search..." %}");
|
||||
}
|
||||
});
|
||||
{% url extensions.amavis_quarantine.main.viewmail as view_url %}
|
||||
bindRows("{{ view_url }}", "{{ current_page }}");
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Quarantine" %}{% endblock %}
|
||||
{% block menu %}
|
||||
@@ -162,15 +101,3 @@ window.addEvent("domready", function() {
|
||||
</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,21 @@ 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");
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% block middletitle %}{% trans "Mail content" %}{% endblock %}
|
||||
{% block menu %}
|
||||
|
||||
@@ -2,7 +2,9 @@ from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.amavis_quarantine.main',
|
||||
(r'^$', 'index'),
|
||||
(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
url(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail',
|
||||
name="viewmail_std"),
|
||||
url(r'^viewmail/$', 'viewmail', name="viewmail_null"),
|
||||
(r'^getmailcontent/(?P<mail_id>[\w\-\+]+)/$', 'getmailcontent'),
|
||||
(r'^viewheaders/(?P<mail_id>[\w\-\+]+)/$', 'viewheaders'),
|
||||
(r'^process/$', 'process'),
|
||||
|
||||
@@ -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,8 @@
|
||||
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())
|
||||
subject = forms.CharField(label=_("Subject"), max_length=255)
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- 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
|
||||
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="6%")
|
||||
_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 IMAPheader(object):
|
||||
addr_exp = re.compile("([^<\(]+)<|\(([^>\)]+)>|\)")
|
||||
name_exp = re.compile("\"(.+)\"")
|
||||
|
||||
@staticmethod
|
||||
def parse_address(value):
|
||||
m = IMAPheader.addr_exp.match(value)
|
||||
if m:
|
||||
name = m.group(1)
|
||||
name = name.strip()
|
||||
m2 = IMAPheader.name_exp.match(name)
|
||||
if m2:
|
||||
return IMAPheader.parse_subject(m2.group(1))
|
||||
return IMAPheader.parse_subject(name)
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def parse_address_list(values):
|
||||
lst = values.split(",")
|
||||
result = ""
|
||||
for addr in lst:
|
||||
if result != "":
|
||||
result += ", "
|
||||
result += IMAPheader.parse_address(addr)
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def parse_from(value):
|
||||
return IMAPheader.parse_address(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_to(value):
|
||||
return IMAPheader.parse_address_list(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_cc(value):
|
||||
return IMAPheader.parse_address_list(value)
|
||||
|
||||
@staticmethod
|
||||
def parse_date(value):
|
||||
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):
|
||||
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, folder=None, order=None):
|
||||
if order:
|
||||
sign = order[:1]
|
||||
criterion = order[1:].upper()
|
||||
if sign == '-':
|
||||
criterion = "REVERSE %s" % criterion
|
||||
else:
|
||||
criterion = "REVERSE DATE"
|
||||
print criterion
|
||||
|
||||
(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 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
|
||||
|
||||
def __init__(self, user, password, baseurl=None, navparams={}, **kwargs):
|
||||
self.mbc = IMAPconnector(user=user, password=password)
|
||||
self.navparams = navparams
|
||||
EmailListing.__init__(self, **kwargs)
|
||||
if baseurl:
|
||||
self.paginator.baseurl = baseurl
|
||||
|
||||
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
|
||||
@@ -0,0 +1,195 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import sys
|
||||
from django.http import HttpResponse
|
||||
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
|
||||
from mailng.lib.email_listing import Email
|
||||
from imap_listing import *
|
||||
from forms import *
|
||||
from templatetags.webextras import *
|
||||
|
||||
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 secure connexion 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"))
|
||||
|
||||
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"]
|
||||
|
||||
def getctx(status, level=1, **kwargs):
|
||||
callername = sys._getframe(level).f_code.co_name
|
||||
ctx = {"status" : status, "callback" : callername}
|
||||
for kw, v in kwargs.iteritems():
|
||||
ctx[kw] = v
|
||||
return ctx
|
||||
|
||||
@login_required
|
||||
def folder(request, name, updatenav=True):
|
||||
if not name:
|
||||
name = "INBOX"
|
||||
order = request.GET.has_key("order") and request.GET["order"] or None
|
||||
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
|
||||
|
||||
lst = ImapListing(request.user.username, request.session["password"],
|
||||
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"],
|
||||
"INBOX", navparams=navp, folder="INBOX")
|
||||
except Exception as exp:
|
||||
return _render_error(request, {"error" : exp})
|
||||
return lst.render(request, empty=True)
|
||||
|
||||
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):
|
||||
header = fetchmail(request, folder, mail_id)
|
||||
if header:
|
||||
from templatetags.webextras import viewm_menu
|
||||
try:
|
||||
pageid = request.session["page"]
|
||||
except KeyError:
|
||||
pageid = "1"
|
||||
menu = viewm_menu("", request.session, mail_id,
|
||||
request.user.get_all_permissions())
|
||||
folder, imapid = header["imapid"].split("/")
|
||||
mailcontent = render_to_string("webmail/viewmail.html", {
|
||||
"header" : header, "folder" : folder, "imapid" : imapid
|
||||
})
|
||||
ctx = getctx("ok", menu=menu, listing=mailcontent)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def getmailcontent(request, folder, mail_id):
|
||||
from mailng.lib.email_listing import Email
|
||||
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
if "class" in msg.keys() and msg["class"] == "unseen":
|
||||
IMAPconnector(request).msgseen(msg["imapid"])
|
||||
email = Email(msg, mode="html", links="1")
|
||||
return email.render(request)
|
||||
|
||||
@login_required
|
||||
def move(request):
|
||||
for arg in ["msgset", "to"]:
|
||||
if not request.GET.has_key(arg):
|
||||
return
|
||||
folder, id = request.GET["msgset"].split("/")
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.move(id, folder, request.GET["to"])
|
||||
return HttpResponse(simplejson.dumps({"status" : "ok"}),
|
||||
mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def empty(request, name):
|
||||
mbc = IMAPconnector(request)
|
||||
mbc.empty(name)
|
||||
return folder(request, name, False)
|
||||
|
||||
def render_compose(request, form, 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
|
||||
})
|
||||
ctx = getctx("ok", level=2, menu=menu, listing=content)
|
||||
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
|
||||
|
||||
@login_required
|
||||
def compose(request):
|
||||
if request.method == "POST":
|
||||
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"]
|
||||
s = smtplib.SMTP(parameters.get("webmail", "SMTP_SERVER"))
|
||||
s.sendmail(msg['From'], [msg['To']], msg.as_string())
|
||||
s.quit()
|
||||
return folder(request, request.session["navparams"]["folder"], False)
|
||||
|
||||
form = ComposeMailForm()
|
||||
form.fields["from_"].initial = request.user.username
|
||||
return render_compose(request, form)
|
||||
|
||||
@login_required
|
||||
def reply(request, folder, mail_id):
|
||||
if request.method == "POST":
|
||||
return
|
||||
msg = fetchmail(request, folder, mail_id, True)
|
||||
email = Email(msg)
|
||||
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
|
||||
form.fields["to"].initial = msg["From"]
|
||||
m = re.match("re\s*:\s*.+", msg["Subject"].lower())
|
||||
if m:
|
||||
form.fields["subject"].initial = msg["Subject"]
|
||||
else:
|
||||
form.fields["subject"].initial = "Re: %s" % msg["Subject"]
|
||||
textheader = msg["From"] + " " + _("wrote:")
|
||||
return render_compose(request, form, textheader, body)
|
||||
@@ -0,0 +1,50 @@
|
||||
{% load i18n %}
|
||||
<style type="text/css">
|
||||
#compose-container {
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
}
|
||||
div.row {
|
||||
clear: both;
|
||||
padding-top: 5px;
|
||||
}
|
||||
div.row label {
|
||||
float: left;
|
||||
width: 120px;
|
||||
font-size: small;
|
||||
padding: 3px 0;
|
||||
}
|
||||
textarea {
|
||||
border: 1px solid #cccccc;
|
||||
background-color: #eee;
|
||||
width: 80%;
|
||||
height: 40px;
|
||||
font-size: medium;
|
||||
}
|
||||
textarea:hover {
|
||||
background-color: #ffffcc;
|
||||
}
|
||||
#id_body {
|
||||
background: white;
|
||||
width: 100%;
|
||||
}
|
||||
input {
|
||||
width: 80%;
|
||||
padding: 0;
|
||||
}
|
||||
#mailheader {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% url extensions.webmail.main.compose as compurl %}
|
||||
<form method="POST" action="{{ compurl }}" id="composemail">
|
||||
<div id="compose-container">
|
||||
<div id="mailheader">
|
||||
{% include "common/genericform.html" %}
|
||||
</div>
|
||||
<textarea id="id_body" name="id_body">
|
||||
{% if bodyheader %}{{ bodyheader }}{% endif %}
|
||||
{% if body %}{{ body }}{% endif %}</textarea>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,5 @@
|
||||
<table id="headers">
|
||||
{% for hdr in headers %}
|
||||
<tr><th width="150px">{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@@ -0,0 +1,297 @@
|
||||
{% extends "common/email_listing.html" %}
|
||||
{% load i18n %}
|
||||
{% block css %}
|
||||
#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;
|
||||
}
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
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 {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.dragging {
|
||||
background: #6AB0C5;
|
||||
}
|
||||
|
||||
tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
navparams = new Hash({
|
||||
{% for k, v in navparams.items %}
|
||||
{{ k }} : "{{ v }}",
|
||||
{% endfor %}
|
||||
});
|
||||
|
||||
updatelisting = function(resp, overflow) {
|
||||
/*if (!$defined(overflow)) {
|
||||
overflow = "auto";
|
||||
}*/
|
||||
if ($defined(resp.menu)) {
|
||||
$("menubar").set("html", resp.menu);
|
||||
}
|
||||
$("listing").set("html", resp.listing);
|
||||
/*if ($defined(resp.navbar)) {
|
||||
$("navbar").set("html", resp.navbar);
|
||||
$("navbar").setStyle("display", "block");
|
||||
$("navbar").getChildren("a").addEvent("click", navbarcallback);
|
||||
}*/
|
||||
$$("a[name=compose]").addEvent("click", loadCompose);
|
||||
$$("a[name=empty]").addEvent("click", empty);
|
||||
}
|
||||
|
||||
empty = function(event) {
|
||||
event.stop();
|
||||
new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) {
|
||||
updatelisting(resp);
|
||||
}}).get();
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
{% url extensions.webmail.main.folder as view_url %}
|
||||
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();
|
||||
}
|
||||
}
|
||||
register_callback("viewmail", function(resp) {
|
||||
updatelisting(resp, "hidden");
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=reply]").addEvent("click", reply);
|
||||
setDivHeight("mailcontent", $("table-container").getSize().y, 0);
|
||||
});
|
||||
|
||||
compose_callback = function(resp) {
|
||||
updatelisting(resp, "hidden");
|
||||
$$("a[name=back]").addEvent("click", loadFolder);
|
||||
$$("a[name=sendmail]").addEvent("click", function(evt) {
|
||||
evt.stop();
|
||||
$("composemail").set("send", {
|
||||
onSuccess: function(res) {
|
||||
updatelisting(JSON.decode(res));
|
||||
$$("tr").addEvent("click", viewmail);
|
||||
}
|
||||
});
|
||||
$("composemail").send();
|
||||
});
|
||||
setDivHeight("id_body", $("mailheader").getSize().y + 20, 0);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {
|
||||
updatelisting(resp, "auto");
|
||||
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");
|
||||
}
|
||||
/*$$("td[class*=draggable]").each(function(item) {
|
||||
item.makeDraggable({
|
||||
droppables: ".droppable",
|
||||
}).addEvents({
|
||||
"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");
|
||||
this.oldelement.getParent().dispose();
|
||||
to = droppable.getFirst("a").get("href");
|
||||
{% url extensions.webmail.main.move as moveurl %}
|
||||
new Request.JSON({url: "{{ moveurl }}", onSuccess: function(resp) {
|
||||
|
||||
}}).get({msgset : this.oldelement.getParent().get("id"),
|
||||
to : to});
|
||||
},
|
||||
"beforeStart": function(item) {
|
||||
var clone = item.clone()
|
||||
.setStyles({"position" : "absolute",
|
||||
"opacity" : 0.6,
|
||||
"width" : "300px",
|
||||
"border" : "1px solid #333",
|
||||
"background" : "white"})
|
||||
.setStyles(item.getCoordinates())
|
||||
.inject(document.body);
|
||||
this.oldelement = this.element;
|
||||
this.element = clone;
|
||||
},
|
||||
"cancel": function(item) {
|
||||
if ($defined(this.oldelement)) {
|
||||
item.dispose();
|
||||
delete(item);
|
||||
this.element = this.oldelement;
|
||||
}
|
||||
}
|
||||
});
|
||||
});*/
|
||||
$$("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();
|
||||
});
|
||||
});
|
||||
|
||||
/*navbarcallback = function(event) {
|
||||
event.stop();
|
||||
current_anchor.setparams(navparams);
|
||||
current_anchor.parse_string(event.target.getParent().get("href")).update();
|
||||
}*/
|
||||
|
||||
foldercallback = function(event) {
|
||||
select(event.target);
|
||||
loadFolder(event, event.target);
|
||||
}
|
||||
|
||||
ajaxListener("INBOX/", updatelisting);
|
||||
$$("a[name=loadfolder]").addEvent("click", foldercallback);
|
||||
/*$("navbar").getChildren("a").addEvent("click", navbarcallback);*/
|
||||
|
||||
{% 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 %}
|
||||
@@ -0,0 +1,25 @@
|
||||
{% load i18n %}
|
||||
{% load webextras %}
|
||||
<style type="text/css">
|
||||
#mailcontent {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
td {
|
||||
font-size: 1em;
|
||||
height: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #F7F9Fb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="table-container">
|
||||
{% render_headers header %}
|
||||
</div>
|
||||
|
||||
{% url extensions.webmail.main.getmailcontent folder imapid as geturl %}
|
||||
<iframe id="mailcontent" src="{{ geturl }}"></iframe>
|
||||
@@ -0,0 +1,101 @@
|
||||
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" : "reply-all",
|
||||
"url" : "",
|
||||
"img" : "/static/pics/reply-all.png",
|
||||
"label" : _("Reply all")},
|
||||
{"name" : "forward",
|
||||
"url" : "",
|
||||
"img" : "/static/pics/alias.png",
|
||||
"label" : _("Forward")},
|
||||
]
|
||||
|
||||
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")}
|
||||
]
|
||||
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})
|
||||
|
||||
@register.simple_tag
|
||||
def render_headers(headers):
|
||||
fields = ["Subject", "From", "To", "Cc", "Date"]
|
||||
result = []
|
||||
for f in fields:
|
||||
if not f in headers.keys():
|
||||
continue
|
||||
try:
|
||||
result += [{"name" : f, "value" : getattr(IMAPheader, "parse_%s" % f.lower())(headers[f])}]
|
||||
except AttributeError:
|
||||
result += [{"name" : f, "value" : headers[f]}]
|
||||
return render_to_string("webmail/headers.html", {
|
||||
"headers" : result
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
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\-\+]+)/content/$', 'getmailcontent'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/reply/$', 'reply'),
|
||||
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
|
||||
(r'^(?P<name>.+)/$', "folder"),
|
||||
)
|
||||
@@ -50,6 +50,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:
|
||||
@@ -63,7 +67,7 @@ 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 decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
|
||||
for encoding in encodings:
|
||||
try:
|
||||
return s.decode(encoding)
|
||||
@@ -78,3 +82,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,187 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import time
|
||||
import re
|
||||
import lxml
|
||||
import lxml.html
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import Template, Context
|
||||
from mailng.lib import _render, decode
|
||||
|
||||
attached_map = {}
|
||||
|
||||
class MBconnector(object):
|
||||
def __init__(self, address, port):
|
||||
self.address = address
|
||||
self.port = port
|
||||
|
||||
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, folder=None, elems_per_page=40, **kwargs):
|
||||
self.folder = folder
|
||||
self.elems_per_page = elems_per_page
|
||||
order = "order" in kwargs.keys() and kwargs["order"] or None
|
||||
self.paginator = Paginator(self.mbc.messages_count(self.folder, order),
|
||||
elems_per_page)
|
||||
|
||||
def render_navbar(self, page):
|
||||
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):
|
||||
page = self.paginator.getpage(pageid)
|
||||
thead = listing = ""
|
||||
if "empty" in kwargs.keys() and not kwargs["empty"]:
|
||||
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,
|
||||
"current_page" : pageid
|
||||
})
|
||||
|
||||
class Email(object):
|
||||
def __init__(self, msg, mode="plain", links="0"):
|
||||
self.attached_map = {}
|
||||
contents = {"html" : "", "plain" : ""}
|
||||
for part in msg.walk():
|
||||
if part.get_content_maintype() == 'multipart':
|
||||
continue
|
||||
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(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 = lxml.html.tostring(html)
|
||||
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,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import inspect
|
||||
import lxml
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
class Column:
|
||||
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):
|
||||
def __str__(self):
|
||||
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:
|
||||
if isinstance(c, SelectionColumn):
|
||||
nrow["cols"] += [{"value" : c.render(row[self.idkey]), "safe" : True}]
|
||||
continue
|
||||
if isinstance(c, ImgColumn):
|
||||
key = "img_%s" % c.name
|
||||
if key in row.keys():
|
||||
nrow["cols"] += [{"value" : c.render(row[key]), "safe" : True, "width" : c.width}]
|
||||
else:
|
||||
nrow["cols"] += [{"width" : c.width}]
|
||||
continue
|
||||
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"]
|
||||
|
||||
nrow["cols"] += [{"value" : self.parse(c.name, row[c.name]),
|
||||
"class" : cssclass,
|
||||
"width" : c.width}]
|
||||
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,6 +1,140 @@
|
||||
window.addEvent('domready', function(){
|
||||
if ($('menubar')) {
|
||||
$('menubar').getElements('li.dropdown').each( function( elem ){
|
||||
parse_menubar('topmenubar');
|
||||
parse_menubar('menubar');
|
||||
/*SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});*/
|
||||
});
|
||||
|
||||
callbacks = {};
|
||||
|
||||
register_callback = function(name, callback) {
|
||||
callbacks[name] = callback;
|
||||
}
|
||||
|
||||
current_anchor = null;
|
||||
|
||||
check_anchor = function() {
|
||||
if (current_anchor.serialized == location.hash) {
|
||||
return;
|
||||
}
|
||||
current_anchor.from_string(location.hash);
|
||||
if (!current_anchor.serialized) {
|
||||
query = current_anchor.deflocation;
|
||||
} else {
|
||||
query = current_anchor.serialized.substring(1);
|
||||
}
|
||||
new Request.JSON({url: query, onSuccess: function(resp) {
|
||||
callback = ($defined(resp.callback)) ? resp.callback : "default";
|
||||
callbacks[callback](resp);
|
||||
}}).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() {
|
||||
location.hash = this.serialize();
|
||||
};
|
||||
|
||||
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();
|
||||
elem.addEvents({
|
||||
@@ -15,10 +149,7 @@ window.addEvent('domready', function(){
|
||||
});
|
||||
});
|
||||
}
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get_iframe_body = function(id) {
|
||||
var saf = navigator.userAgent.match(/Safari/i);
|
||||
@@ -65,3 +196,47 @@ 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
|
||||
- extrah;
|
||||
|
||||
if ($("navbar")) {
|
||||
contentsize -= $("navbar").getSize().y;
|
||||
}
|
||||
if ($("thead")) {
|
||||
contentsize -= $("thead").getSize().y;
|
||||
}
|
||||
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() {
|
||||
|
||||
@@ -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 |
|
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,329 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: right;
|
||||
/*margin: 5px 0 0 5px;*/
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.dropdown ul {
|
||||
display: block;
|
||||
background: #ffffcc;
|
||||
width: 150px;
|
||||
height: 90px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.dropdown a, .dropdown a:visited {
|
||||
color: black !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
table th {
|
||||
background: #CEDBEC;
|
||||
text-align: left;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
table th.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table 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;
|
||||
}
|
||||
@@ -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,61 @@
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<style type="text/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;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: white;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
/*border-top: 1px solid #C8D6E8;*/
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navbar label {
|
||||
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() {
|
||||
if ($("selectall")) {
|
||||
$("selectall").addEvent("click", function(event) {
|
||||
if ($("selectall").checked) {
|
||||
toggleSelection("selection", true);
|
||||
} else {
|
||||
toggleSelection("selection", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
{% block js %}{% endblock %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% block leftcol %}{% endblock %}
|
||||
<div id="listing">{{ listing }}</div>
|
||||
<div class="spacer"></div>
|
||||
<div id="navbar"></div>
|
||||
{% endblock %}
|
||||
@@ -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,10 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{% for c in table.columns %}
|
||||
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
|
||||
{{ c|safe }}
|
||||
<span style="float: right"><img src="" style="display:none" /></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>
|
||||
@@ -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 %}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% if entry.menu %}
|
||||
<li class="dropdown">
|
||||
<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 }}">
|
||||
@@ -26,4 +26,4 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
{% 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/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">
|
||||
<img src="/static/pics/settings.png" border="0" />
|
||||
<label>{% trans "Admin" %}</label>
|
||||
<ul class="links">
|
||||
{% url admin.views.domains as dom_url %}
|
||||
<a href="{{ dom_url }}">
|
||||
<li><img src="/static/pics/domains.png" />{% trans "Domains" %}</li>
|
||||
</a>
|
||||
{% url admin.views.settings as settings_url %}
|
||||
<a href="{{ settings_url }}">
|
||||
<li>
|
||||
<img src="/static/pics/settings.png" border="0"/>
|
||||
{% trans "Settings" %}
|
||||
</li>
|
||||
</a>
|
||||
</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 menubar">
|
||||
|
||||
</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})
|
||||
)
|
||||
|
||||
|
||||
|
||||