summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Gemfile10
-rw-r--r--Gemfile.lock52
-rw-r--r--ag-web.rb160
-rw-r--r--config.ru3
-rw-r--r--config.yml.default11
-rw-r--r--lib/helpers.rb67
-rw-r--r--lib/index.rb122
-rw-r--r--public/css/main.css66
-rw-r--r--public/js/quoting.js30
-rw-r--r--views/index.erb39
-rw-r--r--views/layout.erb123
-rw-r--r--views/listindex.erb15
-rw-r--r--views/listmonth.erb24
-rw-r--r--views/message.erb71
-rw-r--r--views/pagination.erb31
-rw-r--r--views/report.erb75
-rw-r--r--views/reportmail.erb19
-rw-r--r--views/reportsent.erb5
-rw-r--r--views/views.erb4
20 files changed, 928 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e9abc7f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+config.yml \ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..8774b92
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,10 @@
+source 'https://rubygems.org'
+
+gem 'sinatra'
+gem 'sinatra-partial'
+gem 'json'
+gem 'redcarpet'
+gem 'thin'
+gem 'elasticsearch'
+gem 'rinku'
+gem 'pony' \ No newline at end of file
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..eba5798
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,52 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ daemons (1.1.9)
+ elasticsearch (1.0.6)
+ elasticsearch-api (= 1.0.6)
+ elasticsearch-transport (= 1.0.6)
+ elasticsearch-api (1.0.6)
+ multi_json
+ elasticsearch-transport (1.0.6)
+ faraday
+ multi_json
+ eventmachine (1.0.4)
+ faraday (0.9.1)
+ multipart-post (>= 1.2, < 3)
+ json (1.8.2)
+ mail (2.6.3)
+ mime-types (>= 1.16, < 3)
+ mime-types (2.4.3)
+ multi_json (1.10.1)
+ multipart-post (2.0.0)
+ pony (1.11)
+ mail (>= 2.0)
+ rack (1.6.0)
+ rack-protection (1.5.3)
+ rack
+ redcarpet (3.2.2)
+ rinku (1.7.3)
+ sinatra (1.4.5)
+ rack (~> 1.4)
+ rack-protection (~> 1.4)
+ tilt (~> 1.3, >= 1.3.4)
+ sinatra-partial (0.4.0)
+ sinatra
+ thin (1.6.3)
+ daemons (~> 1.0, >= 1.0.9)
+ eventmachine (~> 1.0)
+ rack (~> 1.0)
+ tilt (1.4.1)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ elasticsearch
+ json
+ pony
+ redcarpet
+ rinku
+ sinatra
+ sinatra-partial
+ thin
diff --git a/ag-web.rb b/ag-web.rb
new file mode 100644
index 0000000..850d1db
--- /dev/null
+++ b/ag-web.rb
@@ -0,0 +1,160 @@
+# /usr/bin/env ruby
+# AgWeb -- displaying all the 'golden' flamewars on -dev
+# Alex Legler <a3li@gentoo.org>
+# AGPLv3
+
+require 'bundler/setup'
+require 'yaml'
+require 'sinatra'
+require 'sinatra/partial'
+require 'elasticsearch'
+require 'date'
+require 'pony'
+
+require_relative 'lib/index.rb'
+require_relative 'lib/helpers.rb'
+
+configure do
+ set :partial_template_engine, :erb
+ mime_type :atom, 'application/atom+xml'
+end
+
+$es = Elasticsearch::Client.new(log: false)
+$es.transport.reload_connections!
+
+$config = YAML.load_file('config.yml')
+
+$valid_lists = %w[www-redesign gentoo-alt gentoo-dev gentoo-amd64]
+
+get '/:list/report/:msgid' do
+ return unless list_check
+
+ begin
+ result = get_message(params[:list], params[:msgid])
+
+ if result['hits']['total'] == 0
+ status 404
+ body "Message not found."
+ return
+ end
+
+ result_data = result['hits']['hits'].first
+ @title = "Report %s - %s" % [h(result_data['_source']['subject']), params[:list]]
+
+ erb :report, locals: { message: result_data, list: params[:list] }
+ rescue Exception => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+post '/report' do
+ return unless list_check
+
+ begin
+ result = get_message(params[:list], params[:msgid])
+
+ if result['hits']['total'] == 0
+ status 404
+ body "Message not found."
+ return
+ end
+
+ result_data = result['hits']['hits'].first
+ @title = "Report %s - %s" % [h(result_data['_source']['subject']), params[:list]]
+
+ msg = ''
+ if params[:captcha] == $config['report_captcha']
+ Pony.mail(
+ to: $config['report_addr'],
+ from: 'archives.gentoo.org <postmaster@gentoo.org>',
+ subject: "Reported Message on #{params[:list]}: #{result_data['_source']['subject']}",
+ body: erb(:reportmail, locals: { message: result_data, list: params[:list] }, layout: false)
+ )
+ msg = 'Thanks for your report.'
+ else
+ msg = 'No, Larry does not make that sound (Invalid CAPTCHA). Report not sent.'
+ end
+
+ erb :reportsent, locals: { message: result_data, list: params[:list], msg: msg }
+ rescue Exception => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+get '/:list/' do
+ return unless list_check
+
+ begin
+ result = get_month_listing(params[:list])
+ @title = params[:list]
+ current_monthint = to_monthint(Date.today.year, Date.today.month)
+ puts current_monthint
+
+ erb :listindex, locals: { results: result, list: params[:list], current_monthint: current_monthint }
+ rescue => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+get '/:list/threads/:year-:month/:page?' do
+ return unless list_check
+
+ begin
+ @title = params[:list]
+ current_page = [(params[:page] || 1).to_i, 1].max
+ result = threads_in_month(params[:list], params[:year], params[:month], current_page)
+ max_pages = (result['hits']['total'].to_f / PER_PAGE).ceil
+
+ erb :listmonth, locals: { results: result, list: params[:list], current_page: current_page, max_pages: max_pages, mode: :threads }
+ rescue => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+get '/:list/messages/:year-:month/:page?' do
+ return unless list_check
+
+ begin
+ @title = params[:list]
+ current_page = [(params[:page] || 1).to_i, 1].max
+ result = messages_in_month(params[:list], params[:year], params[:month], current_page)
+ max_pages = (result['hits']['total'].to_f / PER_PAGE).ceil
+
+ erb :listmonth, locals: { results: result, list: params[:list], current_page: current_page, max_pages: max_pages, mode: :messages }
+ rescue => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+get '/:list/message/:msgid' do
+ return unless list_check
+
+ begin
+ result = get_message(params[:list], params[:msgid])
+
+ if result['hits']['total'] == 0
+ status 404
+ body "Message not found."
+ return
+ end
+
+ result_data = result['hits']['hits'].first
+ @title = "%s - %s" % [h(result_data['_source']['subject']), params[:list]]
+ parent_data = get_parent_data(params[:list], result_data['_source']['parent'])
+ child_data = get_child_data(params[:list], params[:msgid])
+
+ erb :message, locals: { message: result_data, list: params[:list], parent: parent_data, children: child_data }
+ rescue Exception => e
+ $stderr.puts e.to_s
+ status 503
+ end
+end
+
+get '/' do
+ erb :index
+end \ No newline at end of file
diff --git a/config.ru b/config.ru
new file mode 100644
index 0000000..e9d20b7
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,3 @@
+require File.expand_path '../ag-web.rb', __FILE__
+
+run Sinatra::Application
diff --git a/config.yml.default b/config.yml.default
new file mode 100644
index 0000000..4e87592
--- /dev/null
+++ b/config.yml.default
@@ -0,0 +1,11 @@
+---
+active_lists:
+ - gentoo-alpha
+ - gentoo-amd64
+frozen_lists:
+ - www-redesign
+report_addr: 'devnull@example.com'
+report_captcha: 'bird'
+report_captcha_q: 'What is the word?'
+report_captcha_hint: 'everybody knows about the word'
+base_url: 'http://archives.gentoo.org/' \ No newline at end of file
diff --git a/lib/helpers.rb b/lib/helpers.rb
new file mode 100644
index 0000000..fe0083a
--- /dev/null
+++ b/lib/helpers.rb
@@ -0,0 +1,67 @@
+require 'date'
+require 'rinku'
+
+helpers do
+ def list_check
+ unless $config['active_lists'].include?(params[:list]) or $config['frozen_lists'].include?(params[:list])
+ status 404
+ body "List not found"
+ return false
+ end
+
+ true
+ end
+
+ def monthint_to_link(monthint)
+ date = DateTime.parse("%s-%s-01" % [monthint[0..3], monthint[4..5]])
+
+ "<a href=\"threads/%s/\">%s</a>" % [date.strftime('%Y-%m'), date.strftime('%Y %B')]
+ end
+
+ def date_format(date)
+ DateTime.iso8601(date).rfc2822
+ end
+
+ def to_monthint(year, month)
+ ("%i%02i" % [year.to_i, month.to_i]).to_i
+ end
+
+ def to_month(year, month)
+ date = DateTime.parse("%s-%s-01" % [year, month])
+ date.strftime('%B %Y')
+ end
+
+ def h(text)
+ Rack::Utils.escape_html(text)
+ end
+
+ def strip_email_domain(str)
+ str.gsub(/@(.*?)(>|$)/) do |s|
+ if $1 == 'gentoo.org'
+ "@g.o#{$2}"
+ elsif $1 == 'lists.gentoo.org'
+ "@l.g.o#{$2}"
+ else
+ "@#{'×' * $1.length}#{$2}"
+ end
+ end
+ end
+
+ def strip_email(str)
+ str.gsub(/([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,10})/) do |email|
+ if $2 == 'gentoo' and $3 == 'org'
+ "#{$1}@g.o"
+ elsif $2 == 'lists.gentoo' and $3 == 'org'
+ "#{$1}@l.g.o"
+ else
+ "#{$1}@#{'×' * $2.length}.#{$3}"
+ end
+ end
+ end
+
+ def linkize(str)
+ Rinku.auto_link(str, :urls, 'rel="nofollow"')
+ end
+
+
+end \ No newline at end of file
diff --git a/lib/index.rb b/lib/index.rb
new file mode 100644
index 0000000..b4ad2b7
--- /dev/null
+++ b/lib/index.rb
@@ -0,0 +1,122 @@
+PER_PAGE = 100
+
+def threads_in_month(list, year, month, page)
+ $es.search(
+ index: 'ml-' + list,
+ size: PER_PAGE,
+ from: PER_PAGE * (page - 1),
+ body: {
+ query: {
+ filtered: {
+ filter: {
+ and:
+ [
+ { term: { month: to_monthint(year, month) } },
+ { missing: { field: 'parent' } }
+ ]
+ }
+ }
+ },
+ sort: {
+ date: 'desc',
+ }
+ }
+ )
+end
+
+def messages_in_month(list, year, month, page)
+ $es.search(
+ index: 'ml-' + list,
+ size: PER_PAGE,
+ from: PER_PAGE * (page - 1),
+ body: {
+ query: {
+ filtered: {
+ filter: {
+ term: { month: to_monthint(year, month) }
+ }
+ }
+ },
+ sort: {
+ date: 'desc',
+ }
+ }
+ )
+end
+
+def get_month_listing(list)
+ $es.search(
+ index: 'ml-' + list,
+ search_type: 'count',
+ size: 0,
+ body: {
+ size: 0,
+ aggs: {
+ messages_by_month: {
+ terms: {
+ field: 'month',
+ size: 0,
+ order: {
+ 'month2': 'desc'
+ }
+ },
+ aggs: {
+ month2: {
+ avg: {
+ field: 'month'
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+end
+
+
+def get_message(list, hash)
+ $es.search(
+ index: 'ml-' + list,
+ size: 1,
+ body: {
+ query: {
+ filtered: {
+ filter: {
+ term: { _id: hash }
+ }
+ }
+ }
+ }
+ )
+end
+
+def get_parent_data(list, parent_id)
+ return nil if parent_id == nil
+
+ parent = $es.search(
+ index: 'ml-' + list,
+ body: { query: { match: { _id: parent_id } } }
+ )
+
+ if parent['hits']['total'] == 0
+ return nil
+ else
+ return parent['hits']['hits'].first
+ end
+end
+
+def get_child_data(list, parent_id)
+ return nil if parent_id == nil
+
+ children = $es.search(
+ index: 'ml-' + list,
+ size: 100,
+ body: { query: { match: { parent: parent_id } } }
+ )
+
+ if children['hits']['total'] == 0
+ return nil
+ else
+ return children['hits']['hits']
+ end
+end \ No newline at end of file
diff --git a/public/css/main.css b/public/css/main.css
new file mode 100644
index 0000000..2827892
--- /dev/null
+++ b/public/css/main.css
@@ -0,0 +1,66 @@
+.ag-text-content {
+ width: 100%;
+ overflow-x: scroll;
+ overflow-y: hidden;
+ display: block;
+}
+
+.ag-message-table {
+ width: 100%;
+ table-layout: fixed;
+}
+
+.ag-message-table td {
+ text-overflow: ellipsis;
+ max-height: 1.2em;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.ag-message-table-date {
+ width: 22%;
+}
+
+.ag-message-table-from {
+ width: 18%;
+}
+
+.ag-quote {
+ color: #999;
+ cursor: row-resize;
+ margin-bottom: -1em;
+ overflow: hidden;
+}
+
+.ag-quote-hidden {
+ height: 2.4em;
+ overflow: hidden;
+ fdisplay: block;
+ -webkit-mask-image: linear-gradient(to bottom, white, transparent);
+ mask-image: linear-gradient(to bottom, white, transparent);
+}
+
+.ag-toggle-quotes {
+ float: right;
+ margin-bottom: .5em;
+}
+
+.ag-message-content {
+ clear: both;
+}
+
+.ag-header-name-col {
+ width: 10em;
+}
+
+.ag-pager {
+ margin: 0;
+}
+
+.ag-html-content {
+ white-space: normal;
+}
+
+.ag-view-selection {
+ margin-bottom: .5em;
+} \ No newline at end of file
diff --git a/public/js/quoting.js b/public/js/quoting.js
new file mode 100644
index 0000000..edf8ec0
--- /dev/null
+++ b/public/js/quoting.js
@@ -0,0 +1,30 @@
+$(function() {
+ var table = $('.ag-header-table');
+
+ if (table === undefined) {
+ return;
+ }
+
+ if ($('.ag-quote').size() == 0) {
+ return;
+ }
+
+ var btn = $("<button class=\"btn btn-xs btn-default ag-toggle-quotes\"><span class=\"fa fa-quote-left\"></span> Toggle quotes</button>");
+ btn.insertAfter(table);
+ btn.click(function() {
+ $('.ag-quote').each(function(index) {
+ $(this).toggleClass('ag-quote-hidden');
+ });
+ });
+
+ $('.ag-quote').each(function(index) {
+ // Don't hide quotes by default. Maybe put in some cookie stuff to remember the choice
+ // $(this).addClass('ag-quote-hidden');
+
+ $(this).click(function() {
+ $('.ag-quote').each(function(index) {
+ $(this).toggleClass('ag-quote-hidden');
+ });
+ })
+ });
+}); \ No newline at end of file
diff --git a/views/index.erb b/views/index.erb
new file mode 100644
index 0000000..4d68cc7
--- /dev/null
+++ b/views/index.erb
@@ -0,0 +1,39 @@
+<h1 class="first-header">Gentoo Mailing List Archives</h1>
+
+<h2>Current Mailing Lists</h2>
+
+<div class="row">
+ <div class="col-xs-12 col-md-6">
+ <div class="list-group">
+ <% $config['active_lists'].each do |list| %>
+ <a href="<%= h list %>/" class="list-group-item"><span class="fa fa-fw fa-archive"></span> <%= h list %></a>
+ <% end %>
+ </div>
+ </div>
+ <div class="col-xs-12 col-md-6">
+ <div class="alert alert-info" role="alert">
+ <strong>How to Participate</strong><br>
+ Please see our <a href="https://www.gentoo.org/main/en/lists.xml" class="alert-link">Mailing List information page</a> for more information on
+ how you can subscribe and participate in the discussions.
+ </div>
+ </div>
+</div>
+
+<h2>Frozen Archives</h2>
+
+<div class="row">
+ <div class="col-xs-12 col-md-6">
+ <div class="list-group">
+ <% $config['frozen_lists'].each do |list| %>
+ <a href="<%= h list %>/" class="list-group-item"><span class="fa fa-fw fa-archive"></span> <%= h list %></a>
+ <% end %>
+ </div>
+ </div>
+ <div class="col-xs-12 col-md-6">
+ <div class="alert alert-warning" role="alert">
+ <strong>Inactive Lists</strong><br>
+ These mailing lists are inactive. You can not post or subscribe to them any more.
+ Archives are provided for future reference.
+ </div>
+ </div>
+</div> \ No newline at end of file
diff --git a/views/layout.erb b/views/layout.erb
new file mode 100644
index 0000000..79d8794
--- /dev/null
+++ b/views/layout.erb
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title><%= "#{h @title} - " if @title %>Gentoo Mailing List Archives</title>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="theme-color" content="#54487a">
+ <meta name="description" content="">
+ <link href="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/bootstrap.min.css" rel="stylesheet" media="screen">
+ <link href="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/tyrian.min.css" rel="stylesheet" media="screen">
+ <link href="/css/main.css" rel="stylesheet" media="screen">
+ <link rel="icon" href="http://www.gentoo.org/favicon.ico" type="image/x-icon">
+ </head>
+ <body>
+ <header>
+ <div class="site-title">
+ <div class="container">
+ <div class="row">
+ <div class="site-title-buttons">
+ <div class="btn-group btn-group-sm">
+ <a href="http://get.gentoo.org/" role="button" class="btn get-gentoo"><span class="fa fa-download"></span> <strong>Get Gentoo!</strong></a>
+ <div class="btn-group btn-group-sm">
+ <a class="btn gentoo-org-sites dropdown-toggle" data-toggle="dropdown" data-target="#" href="#">
+ <span class="glyphicon glyphicon-globe"></span> gentoo.org sites <span class="caret"></span>
+ </a>
+ <ul class="dropdown-menu">
+ <li><a href="http://www.gentoo.org/" title="Main Gentoo website"><span class="fa fa-home fa-fw"></span> gentoo.org</a></li>
+ <li><a href="http://wiki.gentoo.org/" title="Find and contribute documentation"><span class="fa fa-file-text fa-fw"></span> Wiki</a></li>
+ <li><a href="https://bugs.gentoo.org/" title="Report issues and find common issues"><span class="fa fa-bug fa-fw"></span> Bugs</a></li>
+ <li><a href="http://forums.gentoo.org/" title="Discuss with the community"><span class="fa fa-comments-o fa-fw"></span> Forums</a></li>
+ <li><a href="http://packages.gentoo.org/" title="Find software for your Gentoo"><span class="fa fa-hdd-o fa-fw"></span> Packages</a></li>
+ <li class="divider"></li>
+ <li><a href="http://overlays.gentoo.org/" title="Collaborate on maintaining packages"><span class="fa fa-code-fork fa-fw"></span> Overlays</a></li>
+ <li><a href="http://planet.gentoo.org/" title="Find out what's going on in the developer community"><span class="fa fa-rss fa-fw"></span> Planet</a></li>
+ <li><a href="http://archives.gentoo.org/" title="Read up on past discussions"><span class="fa fa-archive fa-fw"></span> Archives</a></li>
+ <li><a href="http://sources.gentoo.org/" title="Browse our source code"><span class="fa fa-code fa-fw"></span> Sources</a></li>
+ <li class="divider"></li>
+ <li><a href="http://infra-status.gentoo.org/" title="Get updates on the services provided by Gentoo"><span class="fa fa-tasks fa-fw"></span> Infra Status</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="logo">
+ <img src="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/site-logo.png" data-at2x="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/site-logo@2x.png" alt="Gentoo Linux Logo" />
+ <span class="site-label">Archives</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <nav class="tyrian-navbar" role="navigation">
+ <div class="container">
+ <div class="row">
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-main-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ </div>
+ <div class="collapse navbar-collapse navbar-main-collapse">
+ <ul class="nav navbar-nav">
+ <% if params[:list] %>
+ <li><a href="/">Home</a></li>
+ <li class="active"><a href="/<%= params[:list] %>/"><%= params[:list] %></a></li>
+ <% else %>
+ <li class="active"><a href="/">Home</a></li>
+ <% end %>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </nav>
+ </header>
+
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12">
+ <%= yield %>
+ </div>
+ </div>
+ </div>
+
+ <footer>
+ <div class="container">
+ <div class="row">
+ <div class="col-xs-12 col-md-offset-2 col-md-7">
+ <p class="spacer">
+ All contents reflect the opinion of the author, not the Gentoo project or the Gentoo Foundation.
+ </p>
+ </div>
+ <div class="col-xs-12 col-md-3">
+ <h3 class="footerhead">Questions or comments?</h3>
+ Please feel free to <a href="http://www.gentoo.org/main/en/contact.xml">contact us</a>.
+ </div>
+ </div>
+ <div class="row">
+ <div class="col-xs-2 col-sm-3 col-md-2">
+ <ul class="footerlinks three-icons">
+ <li><a href="http://twitter.com/gentoo" title="@Gentoo on Twitter"><span class="fa fa-twitter fa-fw"></span></a></li>
+ <li><a href="https://plus.google.com/+Gentoo" title="+Gentoo on Google+"><span class="fa fa-google-plus fa-fw"></span></a></li>
+ <li><a href="https://www.facebook.com/gentoo.org" title="Gentoo on Facebook"><span class="fa fa-facebook fa-fw"></span></a></li>
+ </ul>
+ </div>
+ <div class="col-xs-10 col-sm-9 col-md-10">
+ <strong>&copy; 2001&ndash;2015 Gentoo Foundation, Inc.</strong><br />
+ <small>
+ Gentoo is a trademark of the Gentoo Foundation, Inc.
+ The contents of this document, unless otherwise expressly stated, are licensed under the
+ <a href="http://creativecommons.org/licenses/by-sa/3.0/" rel="license">CC-BY-SA-3.0</a> license.
+ The <a href="http://www.gentoo.org/main/en/name-logo.xml">Gentoo Name and Logo Usage Guidelines</a> apply.
+ </small>
+ </div>
+ </div>
+ </div>
+ </footer>
+
+ <script src="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/jquery.min.js"></script>
+ <script src="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/bootstrap.min.js"></script>
+ <script src="https://1b9a50f4f9de4348cd9f-e703bc50ba0aa66772a874f8c7698be7.ssl.cf5.rackcdn.com/retina.min.js"></script>
+ <script src="/js/quoting.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/views/listindex.erb b/views/listindex.erb
new file mode 100644
index 0000000..ecf6ab7
--- /dev/null
+++ b/views/listindex.erb
@@ -0,0 +1,15 @@
+<h1 class="first-header">Gentoo Archives: <%= list %></h1>
+
+<table class="table">
+ <tr>
+ <th>Month</th>
+ <th>Number of messages</th>
+ </tr>
+ <% results['aggregations']['messages_by_month']['buckets'].each do |month| %>
+ <% next if month['key'].to_i > current_monthint and not params[:allmonths] %>
+ <tr>
+ <td><%= monthint_to_link(month['key'].to_s) %></td>
+ <td><%= month['doc_count'] %></td>
+ </tr>
+ <% end %>
+</table> \ No newline at end of file
diff --git a/views/listmonth.erb b/views/listmonth.erb
new file mode 100644
index 0000000..4cdabe3
--- /dev/null
+++ b/views/listmonth.erb
@@ -0,0 +1,24 @@
+<h1 class="first-header">Gentoo Archives: <%= h list %> in <%= to_month(params[:year], params[:month]) %></h1>
+
+<div class="hidden-xs">
+<%= partial :pagination, locals: { current_page: current_page, max_pages: max_pages } %>
+</div>
+
+<%= partial :views, locals: { list: list, mode: mode } %>
+
+<table class="table table-condensed table-hover ag-message-table">
+ <tr>
+ <th class="ag-message-table-subject">Subject</th>
+ <th class="ag-message-table-from">From</th>
+ <th class="ag-message-table-date">Date</th>
+ </tr>
+ <% results['hits']['hits'].each do |message| %>
+ <tr>
+ <td><a href="../../message/<%= message['_id'] %>"><%= h message['_source']['subject'] %></a></td>
+ <td><%= h message['_source']['from_realname'] %></td>
+ <td><%= date_format message['_source']['date'] %></td>
+ </tr>
+ <% end %>
+</table>
+
+<%= partial :pagination, locals: { current_page: current_page, max_pages: max_pages } %> \ No newline at end of file
diff --git a/views/message.erb b/views/message.erb
new file mode 100644
index 0000000..59d2499
--- /dev/null
+++ b/views/message.erb
@@ -0,0 +1,71 @@
+<h1 class="first-header">Gentoo Archives: <%= list %></h1>
+
+<table class="table table-condensed ag-header-table">
+ <tr>
+ <th class="ag-header-name-col">From:</th>
+ <td><%= h strip_email_domain(message['_source']['from']) %></td>
+ </tr>
+ <tr>
+ <th>To:</th>
+ <td><%= h strip_email_domain(message['_source']['to']) %></td>
+ </tr>
+ <tr>
+ <th>Cc:</th>
+ <td><%= h strip_email_domain(message['_source']['cc']) %></td>
+ </tr>
+ <tr>
+ <th>Subject:</th>
+ <td><strong><%= h message['_source']['subject'] %></strong></td>
+ </tr>
+ <tr>
+ <th>Date:</th>
+ <td><%= date_format message['_source']['date'] %></td>
+ </tr>
+ <% unless parent == nil %>
+ <tr>
+ <th>In Reply to:</th>
+ <td><a href="<%= parent['_id'] %>"><%= parent['_source']['subject'] %></a> by <%= parent['_source']['from'] %></td>
+ </tr>
+ <% end %>
+</table>
+<!-- Message-Id: <%= message['_source']['raw_message_id'] %> -->
+
+<pre class="ag-message-content">
+<%= linkize(strip_email(message['_source']['content'])) %>
+</pre>
+
+<% if message['_source']['attachments'] and not message['_source']['attachments'].empty? %>
+<h3>Attachments</h3>
+
+<table class="table table-condensed ag-attachment-table">
+ <tr>
+ <th>File name</th>
+ <th>MIME type</th>
+ </tr>
+ <% message['_source']['attachments'].each do |attachment| %>
+ <tr>
+ <td><%= h attachment['filename'] %></td>
+ <td><%= h attachment['mime'] %></td>
+ </tr>
+ <% end %>
+</table>
+<% end %>
+
+<% unless children == nil %>
+<h3>Replies</h3>
+
+<table class="table table-condensed ag-replies-table">
+ <tr>
+ <th>Subject</th>
+ <th>Author</th>
+ </tr>
+ <% children.each do |child| %>
+ <tr>
+ <td><a href="<%= child['_id'] %>"><%= h child['_source']['subject'] %></a></td>
+ <td><%= h strip_email_domain(child['_source']['from']) %></td>
+ </tr>
+ <% end %>
+</table>
+<% end %>
+
+<a href="/<%= h list %>/report/<%= message['_id'] %>" class="btn btn-danger btn-xs"><span class="fa fa-ban"></span> Report Message</a> \ No newline at end of file
diff --git a/views/pagination.erb b/views/pagination.erb
new file mode 100644
index 0000000..7d7be9c
--- /dev/null
+++ b/views/pagination.erb
@@ -0,0 +1,31 @@
+<nav class="pull-right">
+ <ul class="pagination ag-pager">
+ <% if current_page == 1 %>
+ <li class="disabled">
+ <a href="#" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ <% else %>
+ <li>
+ <a href="<%= current_page - 1 %>" aria-label="Previous">
+ <span aria-hidden="true">&laquo;</span>
+ </a>
+ <% end %>
+ </li>
+ <% (1..max_pages).each do |page| %>
+ <li <% if page == current_page %><%= 'class="active"' %><% end %>><a href="<%= page %>"><%= page %></a></li>
+ <% end %>
+ <% if current_page == max_pages %>
+ <li class="disabled">
+ <a href="#" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ <% else %>
+ <li>
+ <a href="<%= current_page + 1 %>" aria-label="Next">
+ <span aria-hidden="true">&raquo;</span>
+ </a>
+ <% end %>
+ </li>
+ </ul>
+</nav> \ No newline at end of file
diff --git a/views/report.erb b/views/report.erb
new file mode 100644
index 0000000..559dc23
--- /dev/null
+++ b/views/report.erb
@@ -0,0 +1,75 @@
+<h1 class="first-header">Report Message</h1>
+
+<p>
+ Please confirm that you want to report the message below:
+</p>
+
+<table class="table table-condensed ag-header-table">
+ <tr>
+ <th>Mailing List:</th>
+ <td><%= h list %></td>
+ </tr>
+ <tr>
+ <th class="ag-header-name-col">From:</th>
+ <td><%= h strip_email_domain(message['_source']['from']) %></td>
+ </tr>
+ <tr>
+ <th>To:</th>
+ <td><%= h strip_email_domain(message['_source']['to']) %></td>
+ </tr>
+ <tr>
+ <th>Cc:</th>
+ <td><%= h strip_email_domain(message['_source']['cc']) %></td>
+ </tr>
+ <tr>
+ <th>Subject:</th>
+ <td><strong><%= h message['_source']['subject'] %></strong></td>
+ </tr>
+ <tr>
+ <th>Date:</th>
+ <td><%= date_format message['_source']['date'] %></td>
+ </tr>
+</table>
+
+<div class="alert alert-danger" role="alert">
+ <strong>When not to report a message</strong>
+ <p>
+ Please do <strong>not</strong> report a message just because you disagree with its contents or can't stand the sender, that's what your personal killfile is for.<br>
+ Valid reasons for reporting a message include (obvious) spam; anything from Nigerian princes, duchesses, and the whole lot of them; improperly rendered messages or messages whose contents are missing completely.
+ </p>
+</div>
+
+<hr>
+
+<form action="/report" method="POST">
+ <input type="hidden" name="list" value="<%= list %>">
+ <input type="hidden" name="msgid" value="<%= message['_id'] %>">
+ <div class="form-group">
+ <label for="captcha">El cheapo CAPTCHA</label>
+ <p class="help-block"><%= h $config['report_captcha_q'] %></p>
+ <input type="text" name="captcha" class="form-control" id="captcha" placeholder="<%= h $config['report_captcha_hint'] %>">
+ </div>
+ <div class="form-group">
+ <label for="reason">Reason</label>
+ <select class="form-control" name="reason" id="reason">
+ <option selected disabled>(Select one)</option>
+ <option name="spam">Spam</option>
+ <option name="nigeria_calling">I HAVE PRIVILEGE TO TRANSFER OF SUM OF $65,000,000,000.00 TO YOU</option>
+ <option name="rendering_b0rked">Contents not correctly rendered</option>
+ <option name="content_missing">Contents not displayed at all</option>
+ <option name="other">(other, described below)</option>
+ </select>
+ </div>
+ <div class="form-group">
+ <label for="comments">Additional comments</label>
+ <textarea class="form-control" id="comments" name="comments" rows="3"></textarea>
+ </div>
+ <button type="submit" class="btn btn-danger">Send Report</button>
+</form>
+
+<hr>
+
+<p class="text-muted">
+ <small><strong>Privacy Notice:</strong>
+ Your IP address will be saved to prevent abuse, but not shared with third parties.</small>
+</p> \ No newline at end of file
diff --git a/views/reportmail.erb b/views/reportmail.erb
new file mode 100644
index 0000000..3cdc3d9
--- /dev/null
+++ b/views/reportmail.erb
@@ -0,0 +1,19 @@
+<%= $config['base_url'] %><%= params[:list] %>/message/<%= message['_id'] %>
+
+-------------------------------------------------------------------------------
+ List: <%= params[:list] %>
+ Subject: <%= message['_source']['subject'] %>
+ From: <%= message['_source']['from'] %>
+ Date: <%= date_format message['_source']['date'] %>
+
+ Message-Id: <%= message['_source']['raw_message_id'] %>
+ X-Archives-Hash: <%= message['_id'] %>
+
+ Reason: <%= params[:reason] %>
+ Comments: <%= params[:comments] %>
+
+ Reporter IP: <%= request.ip %>
+
+--
+Much Love,
+Ag \ No newline at end of file
diff --git a/views/reportsent.erb b/views/reportsent.erb
new file mode 100644
index 0000000..a10e0fd
--- /dev/null
+++ b/views/reportsent.erb
@@ -0,0 +1,5 @@
+<h1 class="first-header">Report Message</h1>
+
+<div class="alert alert-info" role="alert">
+ <%= h msg %>
+</div> \ No newline at end of file
diff --git a/views/views.erb b/views/views.erb
new file mode 100644
index 0000000..d644b43
--- /dev/null
+++ b/views/views.erb
@@ -0,0 +1,4 @@
+<div class="btn-group ag-view-selection" role="group" aria-label="Message view selection">
+ <a href="/<%= h list %>/threads/<%= h params[:year] %>-<%= h params[:month] %>/" class="btn btn-<% if mode == :threads%>primary<% else %>default<% end %>">Threads</a>
+ <a href="/<%= h list %>/messages/<%= h params[:year] %>-<%= h params[:month] %>/" class="btn btn-<% if mode == :messages%>primary<% else %>default<% end %>">Messages</a>
+</div> \ No newline at end of file