/src/static/favicon.ico
/src/static/img/apple-touch-icon-*-precomposed.png
/src/systemd/ipfire.org-webapp-*.service
+/src/templates/messages/main.css
/ipfire.org.conf.sample
.DS_Store
Makefile
templates_locationdir = $(templatesdir)/location
+templates_messages_DATA = \
+ src/templates/messages/base.html \
+ src/templates/messages/main.css
+
+templates_messagesdir = $(templatesdir)/messages
+
templates_mirrors_DATA = \
src/templates/mirrors/index.html \
src/templates/mirrors/mirror.html
"launch-campaigns" : self.campaigns.launch_manually,
"run-campaigns" : self.campaigns.run,
"scan-files" : self.releases.scan_files,
+ "send-message" : self.messages.send_cli,
"send-all-messages" : self.messages.queue.send_all,
"test-blacklist" : self.geoip.test_blacklist,
"test-ldap" : self.accounts.test_ldap,
#!/usr/bin/python3
import email
-import email.charset
-import email.mime.nonmultipart
+import email.mime.multipart
+import email.mime.text
import email.utils
import logging
import subprocess
}
namespace.update(kwargs)
- # Create a non-multipart message
- message = email.mime.nonmultipart.MIMENonMultipart(
- "text", "plain", charset="utf-8",
- )
+ # Create an alternating multipart message to show HTML or text
+ message = email.mime.multipart.MIMEMultipart("alternative")
- # Load template
- t = self.template_loader.load("%s.txt" % template_name)
-
- # Render the message
- try:
- message_part = t.generate(**namespace)
+ for extension, mimetype in (("txt", "plain"), ("html", "html")):
+ try:
+ t = self.template_loader.load("%s.%s" % (template_name, extension))
+ except IOError as e:
+ # Ignore if the HTML template does not exist
+ if extension == "html":
+ continue
- # Reset the rendered template when it could not be rendered
- except:
- self.template_loader.reset()
- raise
+ # Raise all other exceptions
+ raise e
- # Parse the message and extract the header
- message_part = email.message_from_string(message_part.decode())
- for k, v in list(message_part.items()):
+ # Render the message
try:
- message.replace_header(k, v)
- except KeyError:
- message.add_header(k, v)
+ message_part = t.generate(**namespace)
+
+ # Reset the rendered template when it could not be rendered
+ except:
+ self.template_loader.reset()
+ raise
+
+ # Parse the message and extract the header
+ message_part = email.message_from_string(message_part.decode())
+
+ for header in message_part:
+ try:
+ message.replace_header(header, message_part[header])
+ except KeyError:
+ message.add_header(header, message_part[header])
- # Do not encode emails in base64
- charset = email.charset.Charset("utf-8")
- charset.body_encoding = email.charset.QP
+ # Create a MIMEText object out of it
+ message_part = email.mime.text.MIMEText(
+ message_part.get_payload(), mimetype)
- # Set payload
- message.set_payload(message_part.get_payload(), charset=charset)
+ # Attach the parts to the mime container.
+ # According to RFC2046, the last part of a multipart message
+ # is preferred.
+ message.attach(message_part)
# Send the message
self.send(recipients, message, priority=priority, headers=headers)
if self.backend.debug:
self.template_loader.reset()
+ async def send_cli(self, template, recipient):
+ """
+ Send a test message from the CLI
+ """
+ account = self.backend.accounts.get_by_mail(recipient)
+
+ return self.send_template(template, recipients=[recipient,],
+ account=account)
+
class Queue(misc.Object):
@property
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <base href="https://www.ipfire.org/">
+ <meta name="viewport" content="width=device-width">
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <title>{% block title %}{% end block %}</title>
+ <style media="all" type="text/css">
+ {% include "main.css" %}
+ </style>
+ </head>
+
+ <body>
+ <span class="preheader">{% block preheader %}{% end preheader %}</span>
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body">
+ <tr>
+ <td class="container">
+ <div class="content">
+ {% block container %}
+ <table role="presentation" class="main">
+ <tr>
+ <td class="logo">
+ <img src="/static/img/ipfire-tux.png" alt="{{ _("IPFire Logo") }}">
+ </td>
+ </tr>
+ <tr>
+ <td class="wrapper">
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+ <tr>
+ <td>
+ {% block content %}{% end block %}
+ </td>
+ </tr>
+ </table>
+ </td>
+ </tr>
+ </table>
+ {% end block %}
+
+ <div class="footer">
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0">
+ <tr>
+ <td class="content-block">
+ <span class="apple-link">{{ _("The IPFire Project" )}}</span>
+
+ <br>
+
+ {% block footer %}
+ {{ _("Don't like these emails?") }}
+ <a href="https://people.ipfire.org/">{{ _("Unsubscribe") }}</a>.
+ {% end block %}
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+ </td>
+ </tr>
+ </table>
+ </body>
+</html>
--- /dev/null
+@import "../../scss/variables";
+
+@import "../../bootstrap/scss/functions";
+@import "../../bootstrap/scss/variables";
+
+
+// Use font sizes in px
+$font-size-base: 18px;
+$small-font-size: 12px;
+
+$h1-font-size: 48px;
+$h2-font-size: 40px;
+$h3-font-size: 36px;
+$h4-font-size: 32px;
+$headings-margin-bottom: 20px;
+
+$paragraph-margin-bottom: 14px;
+
+// Resets
+img {
+ border: none;
+ -ms-interpolation-mode: bicubic;
+ max-width: 100%;
+}
+
+body {
+ background-color: $body-bg;
+ font-family: $font-family-sans-serif;
+ -webkit-font-smoothing: antialiased;
+ font-size: $font-size-base;
+ line-height: $line-height-base;
+ margin: 0;
+ padding: 0;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+}
+
+table {
+ border-collapse: separate;
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+ width: 100%;
+
+ td {
+ font-family: $font-family-sans-serif;
+ font-size: $font-size-base;
+ vertical-align: top;
+ }
+}
+
+// Basic Styling
+
+.body {
+ background-color: $body-bg;
+ width: 100%;
+}
+
+/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
+.container {
+ display: block;
+ margin: 0 auto !important;
+
+ // Center the container
+ max-width: 580px;
+ padding: 10px;
+ width: 580px;
+}
+
+/* This should also be a block element, so that it will fill 100% of the .container */
+.content {
+ box-sizing: border-box;
+ display: block;
+ margin: 0 auto;
+ max-width: 580px;
+ padding: 10px;
+}
+
+// Headers, Footers, Containers
+
+.main {
+ background: $white;
+ color: $dark;
+ border-radius: $card-border-radius;
+ width: 100%;
+
+ .logo {
+ text-align: center;
+
+ img {
+ height: 196px;
+ padding: 24px 0 12px 0;
+ }
+ }
+}
+
+.wrapper {
+ box-sizing: border-box;
+ padding: 20px;
+}
+
+.content-block {
+ padding-bottom: 10px;
+ padding-top: 10px;
+}
+
+.footer {
+ clear: both;
+ margin-top: 10px;
+ text-align: center;
+ width: 100%;
+
+ td, p, span, a {
+ color: $light;
+ font-size: $small-font-size;
+ text-align: center;
+ }
+}
+
+// Typography
+
+h1, h2, h3, h4 {
+ color: $dark;
+ font-family: $font-family-sans-serif;
+ font-weight: $headings-font-weight;
+ line-height: $headings-line-height;
+ margin: 0;
+ margin-bottom: $headings-margin-bottom;
+}
+
+h1 {
+ font-size: $h1-font-size;
+ text-align: center;
+ text-transform: capitalize;
+}
+
+p, ul, ol {
+ font-family: $font-family-sans-serif;
+ font-size: $font-size-base;
+ font-weight: normal;
+ margin: 0;
+ margin-bottom: $paragraph-margin-bottom;
+}
+
+a {
+ color: $link-color;
+ text-decoration: underline;
+}
+
+// Buttons
+
+.btn {
+ box-sizing: border-box;
+ width: 100%;
+
+ > tbody > tr > td {
+ padding-bottom: 15px;
+ }
+
+ table {
+ width: 100%;
+
+ td {
+ background-color: #ffffff;
+ border-radius: $btn-border-radius;
+ text-align: center;
+ }
+ }
+
+ a {
+ width: 100%;
+ background-color: #ffffff;
+ border: 1px solid $link-color;
+ border-radius: $btn-border-radius;
+ box-sizing: border-box;
+ color: $link-color;
+ cursor: pointer;
+ display: inline-block;
+ font-size: $font-size-base;
+ font-weight: $btn-font-weight;
+ margin: 0;
+ padding: $btn-padding-y $btn-padding-x;
+ text-decoration: none;
+ text-transform: uppercase;
+ }
+}
+
+.btn-primary {
+ table td {
+ background-color: $link-color;
+ }
+
+ a {
+ background-color: $link-color;
+ border-color: $link-color;
+ color: #ffffff;
+ }
+}
+
+// Other
+
+.align-center {
+ text-align: center;
+}
+
+.align-right {
+ text-align: right;
+}
+
+.align-left {
+ text-align: left;
+}
+
+.clear {
+ clear: both;
+}
+
+.mt-0 {
+ margin-top: 0;
+}
+
+.mb-0 {
+ margin-bottom: 0;
+}
+
+.preheader {
+ color: transparent;
+ display: none;
+ height: 0;
+ max-height: 0;
+ max-width: 0;
+ opacity: 0;
+ overflow: hidden;
+ mso-hide: all;
+ visibility: hidden;
+ width: 0;
+}
+
+.powered-by a {
+ text-decoration: none;
+}
+
+hr {
+ border: 0;
+ border-bottom: $hr-border-width solid $hr-border-color;
+ margin: 20px 0;
+}
+
+// Make this all mobile-friendly
+
+@media only screen and (max-width: 620px) {
+ table[class=body] {
+ h1 {
+ font-size: $h1-font-size !important;
+ margin-bottom: $headings-margin-bottom !important;
+ }
+
+ p, ul, ol, td, span, a {
+ font-size: $font-size-base !important;
+ }
+
+ .wrapper, .article {
+ padding: 10px !important;
+ }
+
+ .content {
+ padding: 0 !important;
+ }
+
+ .container {
+ padding: 0 !important;
+ width: 100% !important;
+ }
+
+ .main {
+ border-left-width: 0 !important;
+ border-radius: 0 !important;
+ border-right-width: 0 !important;
+ }
+
+ .img-responsive {
+ height: auto !important;
+ max-width: 100% !important;
+ width: auto !important;
+ }
+ }
+}
+
+// Hack for Dark Mode
+
+@media (prefers-dark-interface) {
+ .body {
+ background-color: none;
+ }
+
+ .main {
+ background: $white !important;
+ color: $dark !important;
+ }
+}
+
+// Hack for Outlook
+
+@media all {
+ .ExternalClass {
+ width: 100%;
+
+ &, p, span, font, td, div {
+ line-height: 100%;
+ }
+ }
+
+ .apple-link a {
+ color: inherit !important;
+ font-family: inherit !important;
+ font-size: inherit !important;
+ font-weight: inherit !important;
+ line-height: inherit !important;
+ text-decoration: none !important;
+ }
+
+ #MessageViewBody a {
+ color: inherit;
+ text-decoration: none;
+ font-size: inherit;
+ font-family: inherit;
+ font-weight: inherit;
+ line-height: inherit;
+ }
+
+ .btn-primary {
+ table td:hover {
+ background-color: $link-hover-color !important;
+ }
+
+ a:hover {
+ background-color: $link-hover-color !important;
+ border-color: $link-hover-color !important;
+ }
+ }
+}