@@ -5,7 +5,7 @@ from time import asctime | |||||
from logging import DEBUG | from logging import DEBUG | ||||
from logging.handlers import TimedRotatingFileHandler | from logging.handlers import TimedRotatingFileHandler | ||||
from flask import Flask, request | |||||
from flask import Flask, g, request | |||||
from flask.ext.mako import MakoTemplates, render_template | from flask.ext.mako import MakoTemplates, render_template | ||||
from flup.server.fcgi import WSGIServer | from flup.server.fcgi import WSGIServer | ||||
@@ -15,10 +15,24 @@ app = Flask(__name__) | |||||
MakoTemplates(app) | MakoTemplates(app) | ||||
app.logger.setLevel(DEBUG) | app.logger.setLevel(DEBUG) | ||||
app.logger.addHandler(TimedRotatingFileHandler("logs/app.log", when="D", | |||||
interval=1, backupCount=7)) | |||||
app.logger.addHandler(TimedRotatingFileHandler( | |||||
"logs/app.log", when="D", interval=1, backupCount=7)) | |||||
app.logger.info(u"Flask server started " + asctime()) | app.logger.info(u"Flask server started " + asctime()) | ||||
@app.before_request | |||||
def prepare_cookies(): | |||||
cookie_string = request.environ.get("HTTP_COOKIE") | |||||
g.cookies = parse_cookies(request.script_root, cookie_string) | |||||
g.new_cookies = [] | |||||
@app.after_request | |||||
def add_new_cookies(response): | |||||
if g.new_cookies: | |||||
if "Set-Cookie" in response.headers: | |||||
g.new_cookies.insert(0, response.headers["Set-Cookie"]) | |||||
response.headers["Set-Cookie"] = "; ".join(g.new_cookies) | |||||
return response | |||||
@app.after_request | @app.after_request | ||||
def write_access_log(response): | def write_access_log(response): | ||||
msg = u"%s %s -> %s" | msg = u"%s %s -> %s" | ||||
@@ -27,21 +41,15 @@ def write_access_log(response): | |||||
@app.route("/") | @app.route("/") | ||||
def index(): | def index(): | ||||
root = request.environ["SCRIPT_NAME"] | |||||
cookies = parse_cookies(root, request.environ.get("HTTP_COOKIE")) | |||||
return render_template("index.mako", root=root, environ=request.environ, cookies=cookies) | |||||
return render_template("index.mako") | |||||
@app.route("/settings", methods=["GET", "POST"]) | @app.route("/settings", methods=["GET", "POST"]) | ||||
def settings(): | def settings(): | ||||
root = request.environ["SCRIPT_NAME"] | |||||
cookies = parse_cookies(root, request.environ.get("HTTP_COOKIE")) | |||||
return render_template("settings.mako", root=root, environ=request.environ, cookies=cookies) | |||||
return render_template("settings.mako") | |||||
@app.route("/debug") | @app.route("/debug") | ||||
def debug(): | def debug(): | ||||
root = request.environ["SCRIPT_NAME"] | |||||
cookies = parse_cookies(root, request.environ.get("HTTP_COOKIE")) | |||||
return render_template("debug.mako", root=root, environ=request.environ, cookies=cookies) | |||||
return render_template("debug.mako") | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
WSGIServer(app).run() | WSGIServer(app).run() |
@@ -7,16 +7,14 @@ import re | |||||
from time import time | from time import time | ||||
from earwigbot import exceptions | from earwigbot import exceptions | ||||
from flask import g | |||||
from .misc import get_bot, open_sql_connection | from .misc import get_bot, open_sql_connection | ||||
_descurl = None | |||||
def set_background(context, cookies, selected): | |||||
global _descurl | |||||
def set_background(context, selected): | |||||
conn = open_sql_connection(get_bot(), "globals") | conn = open_sql_connection(get_bot(), "globals") | ||||
if "CopyviosScreenCache" in cookies: | |||||
cache = cookies["CopyviosScreenCache"].value | |||||
if "CopyviosScreenCache" in g.cookies: | |||||
cache = g.cookies["CopyviosScreenCache"].value | |||||
try: | try: | ||||
screen = loads(cache) | screen = loads(cache) | ||||
int(screen["width"]) | int(screen["width"]) | ||||
@@ -32,12 +30,9 @@ def set_background(context, cookies, selected): | |||||
info = _update_url(conn, "background_list", 2, _get_fresh_list) | info = _update_url(conn, "background_list", 2, _get_fresh_list) | ||||
filename, url, descurl, width, height = info | filename, url, descurl, width, height = info | ||||
bg_url = _build_url(screen, filename, url, width, height) | bg_url = _build_url(screen, filename, url, width, height) | ||||
_descurl = descurl | |||||
g.descurl = descurl | |||||
return bg_url | return bg_url | ||||
def get_desc_url(context): | |||||
return _descurl | |||||
def _update_url(conn, service, bg_id, callback): | def _update_url(conn, service, bg_id, callback): | ||||
query1 = "SELECT update_time FROM updates WHERE update_service = ?" | query1 = "SELECT update_time FROM updates WHERE update_service = ?" | ||||
query2 = "SELECT 1 FROM background WHERE background_id = ?" | query2 = "SELECT 1 FROM background WHERE background_id = ?" | ||||
@@ -4,6 +4,8 @@ import base64 | |||||
from Cookie import CookieError, SimpleCookie | from Cookie import CookieError, SimpleCookie | ||||
from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||
from flask import g | |||||
class _CookieManager(SimpleCookie): | class _CookieManager(SimpleCookie): | ||||
MAGIC = "--cpv2" | MAGIC = "--cpv2" | ||||
@@ -40,14 +42,15 @@ class _CookieManager(SimpleCookie): | |||||
def parse_cookies(path, cookies): | def parse_cookies(path, cookies): | ||||
return _CookieManager(path, cookies) | return _CookieManager(path, cookies) | ||||
def set_cookie(headers, cookies, key, value, days=0): | |||||
cookies[key] = value | |||||
def set_cookie(key, value, days=0): | |||||
g.cookies[key] = value | |||||
if days: | if days: | ||||
expires = datetime.utcnow() + timedelta(days=days) | |||||
cookies[key]["expires"] = expires.strftime("%a, %d %b %Y %H:%M:%S GMT") | |||||
cookies[key]["path"] = cookies.path | |||||
headers.append(("Set-Cookie", cookies[key].OutputString())) | |||||
def delete_cookie(headers, cookies, key): | |||||
set_cookie(headers, cookies, key, u"", days=-1) | |||||
del cookies[key] | |||||
expire_dt = datetime.utcnow() + timedelta(days=days) | |||||
expires = expire_dt.strftime("%a, %d %b %Y %H:%M:%S GMT") | |||||
g.cookies[key]["expires"] = expires | |||||
g.cookies[key]["path"] = g.cookies.path | |||||
g.new_cookies.append(g.cookies[key].OutputString()) | |||||
def delete_cookie(key): | |||||
set_cookie(key, u"", days=-1) | |||||
del g.cookies[key] |
@@ -4,19 +4,20 @@ from os.path import expanduser | |||||
from urlparse import parse_qs | from urlparse import parse_qs | ||||
from earwigbot.bot import Bot | from earwigbot.bot import Bot | ||||
from flask import request | |||||
import oursql | import oursql | ||||
_bot = None | _bot = None | ||||
_connections = {} | _connections = {} | ||||
class Query(object): | class Query(object): | ||||
def __init__(self, environ, method="GET"): | |||||
def __init__(self, method="GET"): | |||||
self.query = {} | self.query = {} | ||||
if method == "GET": | if method == "GET": | ||||
parsed = parse_qs(environ["QUERY_STRING"]) | |||||
parsed = parse_qs(request.environ["QUERY_STRING"]) | |||||
elif method == "POST": | elif method == "POST": | ||||
size = int(environ.get("CONTENT_LENGTH", 0)) | |||||
parsed = parse_qs(environ["wsgi.input"].read(size)) | |||||
size = int(request.environ.get("CONTENT_LENGTH", 0)) | |||||
parsed = parse_qs(request.environ["wsgi.input"].read(size)) | |||||
else: | else: | ||||
parsed = {} | parsed = {} | ||||
for key, value in parsed.iteritems(): | for key, value in parsed.iteritems(): | ||||
@@ -50,8 +51,9 @@ def open_sql_connection(bot, dbname): | |||||
conn_args = bot.config.wiki["_copyviosSQL"][dbname] | conn_args = bot.config.wiki["_copyviosSQL"][dbname] | ||||
if "read_default_file" not in conn_args and "user" not in conn_args and "passwd" not in conn_args: | if "read_default_file" not in conn_args and "user" not in conn_args and "passwd" not in conn_args: | ||||
conn_args["read_default_file"] = expanduser("~/.my.cnf") | conn_args["read_default_file"] = expanduser("~/.my.cnf") | ||||
elif "read_default_file" in args: | |||||
args["read_default_file"] = expanduser(args["read_default_file"]) | |||||
elif "read_default_file" in conn_args: | |||||
default_file = expanduser(conn_args["read_default_file"]) | |||||
conn_args["read_default_file"] = default_file | |||||
if "autoping" not in conn_args: | if "autoping" not in conn_args: | ||||
conn_args["autoping"] = True | conn_args["autoping"] = True | ||||
if "autoreconnect" not in conn_args: | if "autoreconnect" not in conn_args: | ||||
@@ -1,17 +1,18 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
from flask import g | |||||
from markupsafe import escape | from markupsafe import escape | ||||
from .cookies import set_cookie, delete_cookie | from .cookies import set_cookie, delete_cookie | ||||
from .misc import get_bot, Query | from .misc import get_bot, Query | ||||
from .sites import get_sites | from .sites import get_sites | ||||
def main(context, environ, headers, cookies): | |||||
query = Query(environ, method="POST") | |||||
def main(context): | |||||
query = Query(method="POST") | |||||
if query.action == "set": | if query.action == "set": | ||||
status = _do_set(query, headers, cookies) | |||||
status = _do_set(query) | |||||
elif query.action == "delete": | elif query.action == "delete": | ||||
status = _do_delete(query, headers, cookies) | |||||
status = _do_delete(query) | |||||
else: | else: | ||||
status = None | status = None | ||||
@@ -19,37 +20,39 @@ def main(context, environ, headers, cookies): | |||||
langs, projects = get_sites(bot) | langs, projects = get_sites(bot) | ||||
return bot, status, langs, projects | return bot, status, langs, projects | ||||
def _do_set(query, headers, cookies): | |||||
def _do_set(query): | |||||
cookies = g.cookies | |||||
changes = set() | changes = set() | ||||
if query.lang: | if query.lang: | ||||
key = "CopyviosDefaultLang" | key = "CopyviosDefaultLang" | ||||
if key not in cookies or cookies[key].value != query.lang: | if key not in cookies or cookies[key].value != query.lang: | ||||
set_cookie(headers, cookies, key, query.lang, 1095) | |||||
set_cookie(key, query.lang, 1095) | |||||
changes.add("site") | changes.add("site") | ||||
if query.project: | if query.project: | ||||
key = "CopyviosDefaultProject" | key = "CopyviosDefaultProject" | ||||
if key not in cookies or cookies[key].value != query.project: | if key not in cookies or cookies[key].value != query.project: | ||||
set_cookie(headers, cookies, key, query.project, 1095) | |||||
set_cookie(key, query.project, 1095) | |||||
changes.add("site") | changes.add("site") | ||||
if query.background: | if query.background: | ||||
key = "CopyviosBackground" | key = "CopyviosBackground" | ||||
if key not in cookies or cookies[key].value != query.background: | if key not in cookies or cookies[key].value != query.background: | ||||
set_cookie(headers, cookies, key, query.background, 1095) | |||||
delete_cookie(headers, cookies, "EarwigBackgroundCache") | |||||
set_cookie(key, query.background, 1095) | |||||
delete_cookie("EarwigBackgroundCache") | |||||
changes.add("background") | changes.add("background") | ||||
if changes: | if changes: | ||||
changes = ", ".join(sorted(list(changes))) | changes = ", ".join(sorted(list(changes))) | ||||
return "Updated {0}.".format(changes) | return "Updated {0}.".format(changes) | ||||
return None | return None | ||||
def _do_delete(query, headers, cookies): | |||||
def _do_delete(query): | |||||
cookies = g.cookies | |||||
if query.cookie in cookies: | if query.cookie in cookies: | ||||
delete_cookie(headers, cookies, query.cookie.encode("utf8")) | |||||
delete_cookie(query.cookie.encode("utf8")) | |||||
template = u'Deleted cookie <b><span class="mono">{0}</span></b>.' | template = u'Deleted cookie <b><span class="mono">{0}</span></b>.' | ||||
return template.format(escape(query.cookie)) | return template.format(escape(query.cookie)) | ||||
elif query.all: | elif query.all: | ||||
number = len(cookies) | number = len(cookies) | ||||
for cookie in cookies.values(): | for cookie in cookies.values(): | ||||
delete_cookie(headers, cookies, cookie.key) | |||||
delete_cookie(cookie.key) | |||||
return "Deleted <b>{0}</b> cookies.".format(number) | return "Deleted <b>{0}</b> cookies.".format(number) | ||||
return None | return None |
@@ -1,11 +1,10 @@ | |||||
<%include file="/support/header.mako" args="title='Debug - Earwig\'s Copyvio Detector', root=root, cookies=cookies"/> | |||||
<%include file="/support/header.mako" args="title='Debug - Earwig\'s Copyvio Detector'"/> | |||||
<%! from flask import request %>\ | |||||
<ul> | <ul> | ||||
% for key, value in environ.items(): | |||||
% for key, value in request.environ.items(): | |||||
% if key not in ["wsgi.input", "wsgi.errors", "PATH"]: | % if key not in ["wsgi.input", "wsgi.errors", "PATH"]: | ||||
<li><b>${key}</b>: ${value | h}</li> | <li><b>${key}</b>: ${value | h}</li> | ||||
% elif key == "wsgi.input": | |||||
<li><b>${key}</b>: ${value.read(int(environ.get("CONTENT_LENGTH", 0))) | h}</li> | |||||
% endif | % endif | ||||
% endfor | % endfor | ||||
</ul> | </ul> | ||||
<%include file="/support/footer.mako" args="cookies=cookies"/> | |||||
<%include file="/support/footer.mako"/> |
@@ -1,4 +1,4 @@ | |||||
<%include file="/support/header.mako" args="title='Earwig\'s Copyvio Detector', root=root, cookies=cookies"/> | |||||
<%include file="/support/header.mako" args="title='Earwig\'s Copyvio Detector'"/> | |||||
<%namespace module="copyvios" import="main, highlight_delta"/>\ | <%namespace module="copyvios" import="main, highlight_delta"/>\ | ||||
<%namespace module="copyvios.misc" import="urlstrip"/>\ | <%namespace module="copyvios.misc" import="urlstrip"/>\ | ||||
<% | <% | ||||
@@ -158,4 +158,4 @@ | |||||
</div> | </div> | ||||
</div> | </div> | ||||
% endif | % endif | ||||
<%include file="/support/footer.mako" args="cookies=cookies"/> | |||||
<%include file="/support/footer.mako"/> |
@@ -1,21 +1,25 @@ | |||||
<%namespace module="copyvios.settings" import="main"/>\ | |||||
<% bot, status, langs, projects = main(environ, headers, cookies) %>\ | |||||
<%include file="/support/header.mako" args="title='Settings - Earwig\'s Copyvio Detector', root=root, cookies=cookies"/> | |||||
<%! from json import dumps, loads %>\ | |||||
<%! | |||||
from json import dumps, loads | |||||
from flask import g, request | |||||
from copyvios.settings import main | |||||
%>\ | |||||
<% bot, status, langs, projects = main() %>\ | |||||
<%include file="/support/header.mako" args="title='Settings - Earwig\'s Copyvio Detector'"/> | |||||
% if status: | % if status: | ||||
<div id="info-box" class="green-box"> | <div id="info-box" class="green-box"> | ||||
<p>${status}</p> | <p>${status}</p> | ||||
</div> | </div> | ||||
% endif | % endif | ||||
<p>This page contains some configurable options for the copyvio detector. Settings are saved as cookies. You can view and delete all cookies generated by this site at the bottom of this page.</p> | <p>This page contains some configurable options for the copyvio detector. Settings are saved as cookies. You can view and delete all cookies generated by this site at the bottom of this page.</p> | ||||
<form action="${environ['REQUEST_URI']}" method="post"> | |||||
<form action="${request.base_url}" method="post"> | |||||
<table> | <table> | ||||
<tr> | <tr> | ||||
<td>Default site:</td> | <td>Default site:</td> | ||||
<td> | <td> | ||||
<span class="mono">http://</span> | <span class="mono">http://</span> | ||||
<select name="lang"> | <select name="lang"> | ||||
<% selected_lang = cookies["CopyviosDefaultLang"].value if "CopyviosDefaultLang" in cookies else bot.wiki.get_site().lang %>\ | |||||
<% selected_lang = g.cookies["CopyviosDefaultLang"].value if "CopyviosDefaultLang" in g.cookies else bot.wiki.get_site().lang %>\ | |||||
% for code, name in langs: | % for code, name in langs: | ||||
% if code == selected_lang: | % if code == selected_lang: | ||||
<option value="${code | h}" selected="selected">${name}</option> | <option value="${code | h}" selected="selected">${name}</option> | ||||
@@ -26,7 +30,7 @@ | |||||
</select> | </select> | ||||
<span class="mono">.</span> | <span class="mono">.</span> | ||||
<select name="project"> | <select name="project"> | ||||
<% selected_project = cookies["CopyviosDefaultProject"].value if "CopyviosDefaultProject" in cookies else bot.wiki.get_site().project %>\ | |||||
<% selected_project = g.cookies["CopyviosDefaultProject"].value if "CopyviosDefaultProject" in g.cookies else bot.wiki.get_site().project %>\ | |||||
% for code, name in projects: | % for code, name in projects: | ||||
% if code == selected_project: | % if code == selected_project: | ||||
<option value="${code | h}" selected="selected">${name}</option> | <option value="${code | h}" selected="selected">${name}</option> | ||||
@@ -44,7 +48,7 @@ | |||||
("potd", 'Use the current Commons Picture of the Day, unfiltered. Certain POTDs may be unsuitable as backgrounds due to their aspect ratio or subject matter.'), | ("potd", 'Use the current Commons Picture of the Day, unfiltered. Certain POTDs may be unsuitable as backgrounds due to their aspect ratio or subject matter.'), | ||||
("plain", "Use a plain background."), | ("plain", "Use a plain background."), | ||||
] | ] | ||||
selected = cookies["CopyviosBackground"].value if "CopyviosBackground" in cookies else "list" | |||||
selected = g.cookies["CopyviosBackground"].value if "CopyviosBackground" in g.cookies else "list" | |||||
%>\ | %>\ | ||||
% for i, (value, desc) in enumerate(background_options): | % for i, (value, desc) in enumerate(background_options): | ||||
<tr> | <tr> | ||||
@@ -67,11 +71,11 @@ | |||||
</table> | </table> | ||||
</form> | </form> | ||||
<h2>Cookies</h2> | <h2>Cookies</h2> | ||||
% if cookies: | |||||
% if g.cookies: | |||||
<table> | <table> | ||||
<% cookie_order = ["CopyviosDefaultProject", "CopyviosDefaultLang", "CopyviosBackground", "CopyviosShowDetails", "CopyviosScreenCache"] %>\ | <% cookie_order = ["CopyviosDefaultProject", "CopyviosDefaultLang", "CopyviosBackground", "CopyviosShowDetails", "CopyviosScreenCache"] %>\ | ||||
% for key in [key for key in cookie_order if key in cookies]: | |||||
<% cookie = cookies[key] %>\ | |||||
% for key in [key for key in cookie_order if key in g.cookies]: | |||||
<% cookie = g.cookies[key] %>\ | |||||
<tr> | <tr> | ||||
<td><b><span class="mono">${key | h}</span></b></td> | <td><b><span class="mono">${key | h}</span></b></td> | ||||
% try: | % try: | ||||
@@ -85,7 +89,7 @@ | |||||
<td><span class="mono">${cookie.value | h}</span></td> | <td><span class="mono">${cookie.value | h}</span></td> | ||||
% endtry | % endtry | ||||
<td> | <td> | ||||
<form action="${environ['REQUEST_URI']}" method="post"> | |||||
<form action="${request.base_url}" method="post"> | |||||
<input type="hidden" name="action" value="delete"> | <input type="hidden" name="action" value="delete"> | ||||
<input type="hidden" name="cookie" value="${key | h}"> | <input type="hidden" name="cookie" value="${key | h}"> | ||||
<button type="submit">Delete</button> | <button type="submit">Delete</button> | ||||
@@ -95,7 +99,7 @@ | |||||
% endfor | % endfor | ||||
<tr> | <tr> | ||||
<td> | <td> | ||||
<form action="${environ['REQUEST_URI']}" method="post"> | |||||
<form action="${request.base_url}" method="post"> | |||||
<input type="hidden" name="action" value="delete"> | <input type="hidden" name="action" value="delete"> | ||||
<input type="hidden" name="all" value="1"> | <input type="hidden" name="all" value="1"> | ||||
<button type="submit">Delete all</button> | <button type="submit">Delete all</button> | ||||
@@ -106,4 +110,4 @@ | |||||
% else: | % else: | ||||
<p>No cookies!</p> | <p>No cookies!</p> | ||||
% endif | % endif | ||||
<%include file="/support/footer.mako" args="cookies=cookies"/> | |||||
<%include file="/support/footer.mako"/> |
@@ -1,11 +1,10 @@ | |||||
<%page args="cookies"/>\ | |||||
<%namespace module="copyvios.background" import="get_desc_url"/>\ | |||||
<%! from flask import g %>\ | |||||
</div> | </div> | ||||
<div id="footer"> | <div id="footer"> | ||||
<p>Copyright © 2009–2014 <a href="//en.wikipedia.org/wiki/User:The_Earwig">Ben Kurtovic</a> • \ | <p>Copyright © 2009–2014 <a href="//en.wikipedia.org/wiki/User:The_Earwig">Ben Kurtovic</a> • \ | ||||
<a href="https://github.com/earwig/copyvios">View Source</a> • \ | <a href="https://github.com/earwig/copyvios">View Source</a> • \ | ||||
% if ("CopyviosBackground" in cookies and cookies["CopyviosBackground"].value in ["potd", "list"]) or "CopyviosBackground" not in cookies: | |||||
<a href="${get_desc_url() | h}">Background</a> • \ | |||||
% if ("CopyviosBackground" in g.cookies and g.cookies["CopyviosBackground"].value in ["potd", "list"]) or "CopyviosBackground" not in g.cookies: | |||||
<a href="${g.descurl | h}">Background</a> • \ | |||||
% endif | % endif | ||||
<a href="http://validator.w3.org/check?uri=referer">Valid HTML5</a> | <a href="http://validator.w3.org/check?uri=referer">Valid HTML5</a> | ||||
</p> | </p> | ||||
@@ -1,24 +1,27 @@ | |||||
<%page args="title, root, cookies"/>\ | |||||
<%namespace module="copyvios.background" import="set_background"/>\ | |||||
<%page args="title"/>\ | |||||
<%! | |||||
from flask import g, request | |||||
from copyvios.background import set_background | |||||
%>\ | |||||
<!DOCTYPE html> | <!DOCTYPE html> | ||||
<html lang="en"> | <html lang="en"> | ||||
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<title>${title}</title> | <title>${title}</title> | ||||
<link rel="stylesheet" href="${root}/static/style.min.css" type="text/css" /> | |||||
<script src="${root}/static/script.min.js" type="text/javascript"></script> | |||||
<link rel="stylesheet" href="${request.script_root}/static/style.min.css" type="text/css" /> | |||||
<script src="${request.script_root}/static/script.min.js" type="text/javascript"></script> | |||||
</head> | </head> | ||||
<% selected = cookies["CopyviosBackground"].value if "CopyviosBackground" in cookies else "list" %>\ | |||||
<% selected = g.cookies["CopyviosBackground"].value if "CopyviosBackground" in g.cookies else "list" %>\ | |||||
% if selected == "plain": | % if selected == "plain": | ||||
<body style="background-image: url('${root}/static/background.png');"> | |||||
<body style="background-image: url('${request.script_root}/static/background.png');"> | |||||
% else: | % else: | ||||
<body onload="update_screen_size()" style="background-image: url('${set_background(cookies, selected) | h}'); background-size: cover;"> | |||||
<body onload="update_screen_size()" style="background-image: url('${set_background(selected) | h}'); background-size: cover;"> | |||||
% endif | % endif | ||||
<div id="header"> | <div id="header"> | ||||
<table id="heading"> | <table id="heading"> | ||||
<tr> | <tr> | ||||
<td id="head-home"><a id="a-home" href="${root}">Earwig's Copyvio Detector</a></td> | |||||
<td id="head-settings"><a id="a-settings" href="${root}/settings">Settings</a></td> | |||||
<td id="head-home"><a id="a-home" href="${request.script_root}">Earwig's Copyvio Detector</a></td> | |||||
<td id="head-settings"><a id="a-settings" href="${request.script_root}/settings">Settings</a></td> | |||||
</tr> | </tr> | ||||
</table> | </table> | ||||
</div> | </div> | ||||