Import depuis le home de lhark

This commit is contained in:
guimoz 2017-01-26 23:32:27 +01:00
commit b8664c1af2
32 changed files with 1043 additions and 0 deletions

2
README Normal file
View file

@ -0,0 +1,2 @@
Le serveur a besoin des droits en écriture sur la base SQLite3 et le répertoire
la contenant.

2
assholes Normal file
View file

@ -0,0 +1,2 @@
Luc Absil
Louis-Guillaume Dubois

6
immunity Normal file
View file

@ -0,0 +1,6 @@
Goulven Kermarec
Lazare Olivry
Brieuc Lacroix
Cyriaque Millot
Juliette Tibayrenc
Siqi Liu

BIN
players.db Normal file

Binary file not shown.

BIN
players.db.bak Normal file

Binary file not shown.

417
roulette.py Normal file
View file

@ -0,0 +1,417 @@
# -*- coding: utf8 -*
from flask import Flask, request, session, g, redirect, url_for, \
abort, render_template, flash
from functools import wraps
from contextlib import closing
import sqlite3
import MySQLdb as mdb
from time import time, localtime, strftime
import locale
import random
# configuration
DEBUG = True
SECRET_KEY = "\xf3'\xd2\xf7\xa4[.h\x8e\x11|\xda\x00\x9fyS\xfe\xb3(!\x91'6\x16"
USERNAME = 'admin'
PASSWORD = 'pipo'
SQLITE_FILENAME = '/var/www/roulette/players.db'
SQLITE_SCHEMA = 'schema.sql'
MYSQL_HOST = 'mysql.rez'
MYSQL_USER = 'rezo_admin_ro'
MYSQL_PASSWORD = 'rezopaspipo'
MYSQL_DB = 'rezo_admin'
BAN_DURATION = 30. * 60.
IMMUNITY_FILE = '/var/www/roulette/immunity'
ASSHOLES_FILE = '/var/www/roulette/assholes'
IMMUNITY = [
'Lazare Olivry',
'Brieuc Lacroix',
'Elliot Butty',
'Jean-Christophe Carli',
'Juliette Tibayrenc',
'Elise Laurent',
'Goulven Kermarec',
'Siqi Liu',
]
ASSHOLES = []
app = Flask(__name__)
app.config.from_object(__name__)
app.secret_key = SECRET_KEY
random.seed(time())
locale.setlocale(locale.LC_ALL, 'fr_FR.utf8')
# Utilisation de la base SQLite
def connect_sqlite():
return sqlite3.connect(SQLITE_FILENAME)
def init_db():
# Initialisation de la base SQLite
with closing(connect_sqlite()) as con_sqlite:
with app.open_resource('schema.sql') as f:
con_sqlite.cursor().executescript(f.read())
con_sqlite.commit()
# Connexion à la base SQLite locale
con_sqlite = connect_sqlite()
cur_sqlite = con_sqlite.cursor()
# Connexion à la base MySQL sur babel
con_mysql = mdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB, \
charset='utf8', use_unicode=True)
cur_mysql = con_mysql.cursor(mdb.cursors.DictCursor)
# Remplissage de la table players à partir de la table utilisateurs
cur_mysql.execute("""select id,prenom,nom from utilisateurs
where etat='STATE_ACTIVE' and ecole_id=1 and id<>1
and typeUtilisateur='membre'""")
rows = cur_mysql.fetchall()
for row in rows:
if row['prenom'] + ' ' + row['nom'] in IMMUNITY:
print(row)
cur_sqlite.execute("""insert into players values (?,?,?,?)""", \
((row["id"]), row["prenom"], row["nom"], 0))
# Remplissage de la table ip à partir de la table equipements
cur_mysql.execute("""select equipements.id,utilisateurs.id,equipements.ip
from utilisateurs
inner join equipements on utilisateurs.id=equipements.utilisateur_id
where utilisateurs.ecole_id=1 and utilisateurs.id<>1
and utilisateurs.etat='STATE_ACTIVE' and equipements.etat='STATE_ACTIVE'
and utilisateurs.typeUtilisateur='membre'""")
rows = cur_mysql.fetchall()
for row in rows:
print(row)
cur_sqlite.execute("""insert into machines values (?,?,?)""", \
(row["id"], row["utilisateurs.id"], row["ip"]))
con_sqlite.commit()
cur_sqlite.close()
cur_mysql.close()
def duration_format(seconds):
hours = seconds / 3600
seconds -= 3600*hours
minutes = seconds / 60
seconds -= 60*minutes
s_str = seconds <= 1 and 'seconde' or 'secondes'
m_str = minutes <= 1 and 'minute' or 'minutes'
h_str = hours <= 1 and 'heure' or 'heures'
if hours == 0:
if minutes == 0:
return '%01d %s' % (seconds, s_str)
return '%01d %s et %01d %s' % (minutes, m_str, seconds, s_str)
return '%01d %s, %01d %s et %01d %s' % (hours, h_str, minutes, m_str, seconds, s_str)
def get_ip():
return request.remote_addr
def get_player(player_id):
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select id,firstname,name,ban_end from players
where id=(?)""", [player_id])
row = cur.fetchone()
con.close()
return {'id': row[0], 'firstname': row[1], 'name': row[2], 'ban_end': row[3]}
def get_player_from_ip(ip):
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select players.id,players.firstname,players.name,
machines.id,machines.ip,players.ban_end
from players
inner join machines on players.id=machines.player_id
where machines.ip=(?)""", [ip])
row = cur.fetchone()
con.close()
user = None
if row is not None:
user = {'id': row[0], 'firstname': row[1], 'name': row[2], \
'machine_id': row[3], 'ip': row[4], 'ban_end': row[5]}
return user
def get_player_from_full_name(firstname, name):
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select players.id,players.firstname,players.name,
machines.id,machines.ip,players.ban_end
from players
inner join machines on players.id=machines.player_id
where players.firstname=(?) and players.name=(?)""", [firstname, name])
row = cur.fetchone()
con.close()
user = None
if row is not None:
user = {'id': row[0], 'firstname': row[1], 'name': row[2], \
'machine_id': row[3], 'ip': row[4], 'ban_end': row[5]}
return user
def is_banned(user_id):
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select ban_end from players where id=(?)""", [user_id])
ban_end = cur.fetchone()[0]
con.close()
return time() < ban_end
def playable_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
user = get_player_from_ip(get_ip())
# Attention : un utilisateur inscrit ne peut pas être forcé à être
# désinscrit s'il n'enlève pas son cookie de session. On évite la
# réexécution de la requête.
if 'subscribed' not in session or not session['subscribed']:
session['subscribed'] = user is not None
if not session['subscribed']:
return render_template('not_subscribed.html')
# Un utilisateur banni ne peut pas jouer
if user and is_banned(user['id']):
return banned()
return f(*args, **kwargs)
return decorated_function
def get_players_not_banned():
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select id,firstname,name from players
where (?) > ban_end """, [time()])
rows = cur.fetchall()
con.close()
return [{'id': row[0], 'firstname': row[1], 'name': row[2]} for row in rows]
def cheat(player_id, target_id):
success = random.choice([True, False])
try:
ok = [line.strip().partition(' ') for line in IMMUNITY]
ok = [get_player_from_full_name(names[0], names[2])['id'] for names in ok]
ko = [line.strip().partition(' ') for line in ASSHOLES]
ko = [get_player_from_full_name(names[0], names[2])['id'] for names in ko]
if target_id in ko:
success = True
elif player_id in ko:
success = False
elif target_id in ok:
success = False
except TypeError:
pass
return success
def ban(player_id, target_id, success):
player = get_player(player_id)
target = get_player(target_id)
banned_player = success and target or player
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select id,ban_end from players
where id=(?)""", [banned_player['id']])
ban_end = cur.fetchone()[0]
ban_end = time() + BAN_DURATION
cur.execute("""update players set ban_end=(?)
where id=(?)""", [ban_end, banned_player['id']])
cur.execute("""insert into bans (player_id,target_id,success,time)
values (?,?,?,?)""", [player['id'], target['id'], \
success and 1 or 0, time()])
con.commit()
con.close()
def unban(player_id):
con = connect_sqlite()
cur = con.cursor()
cur.execute("""update players set ban_end=(?)
where id=(?)""", [time() - BAN_DURATION, player_id])
con.commit()
con.close()
def get_bans(player_id):
con = connect_sqlite()
cur = con.cursor()
# Bannissements concernant le joueur :
cur.execute("""select player_id,target_id,success,time from bans
where target_id=(?)
or player_id=(?)""", [player_id, player_id])
rows = cur.fetchall()
con.close()
return [{'player_id': row[0], 'target_id': row[1], \
'success': row[2], 'time': row[3]} for row in rows]
def banned():
player = get_player_from_ip(get_ip())
last_ban = sorted(get_bans(player['id']), key=lambda p: p['time'], \
reverse=False)[-1]
if last_ban['target_id'] == player['id'] and last_ban['success'] == 1:
source = get_player(last_ban['player_id'])
explanation = u'Tu t\'es fait bannir par %s %s.' \
% (source['firstname'], source['name'])
else:
explanation = u'Tu t\'es banni toi-même, pas de chance...'
timeleft = duration_format(int(player['ban_end'] - time()))
return render_template('banned.html', \
explanation=explanation, timeleft=timeleft)
@app.route('/banned_ip')
def banned_ip():
# Liste des ip pour récupération par babel et plop
if get_ip() not in ['10.7.0.39', '10.7.0.254' ,'10.13.0.1', '10.69.8.5' ,'10.69.2.219']:
abort(403)
con = connect_sqlite()
cur = con.cursor()
cur.execute("""select machines.ip from players
inner join machines on players.id=machines.player_id
where players.ban_end>(?)""", [time()])
rows = cur.fetchall()
con.close()
return '\n'.join([row[0] for row in rows])
@app.route('/')
@playable_required
def home():
ip = get_ip()
player = get_player_from_ip(ip)
#if ip not in ['10.69.8.5', '10.69.8.202']:
# abort(403)
if session.get('logged_in'):
pass
bans = sorted(get_bans(player['id']), \
key=lambda ban: ban['time'], \
reverse=True)
bans_hist = []
for ban in bans:
date = strftime('%Hh%M (%A)', localtime(ban['time']))
source = get_player(ban['player_id'])
target = get_player(ban['target_id'])
if target['id'] == player['id']:
if ban['success']:
entry = ('ban', u'%s : %s %s a réussi à t\'avoir.' \
% (date, source['firstname'], source['name']))
else:
entry = ('warn', u'%s : %s %s a essayé de te bannir, en vain.' \
% (date, source['firstname'], source['name']))
else:
if ban['success']:
entry = ('ok', u'%s : Tu as banni %s %s avec succès.' \
% (date, target['firstname'], target['name']))
else:
entry = ('ban', u'%s : Tu as échoué en voulant bannir %s %s.' \
% (date, target['firstname'], target['name']))
bans_hist.append(entry)
return render_template('home.html', bans_hist=bans_hist)
@app.route('/jouer', methods=['GET', 'POST'])
@playable_required
def play():
ip = get_ip()
player = get_player_from_ip(ip)
# Traitement de la requête de bannissement
if request.method == 'POST':
target_id = request.form['target_id']
if target_id != 'none':
if is_banned(target_id):
flash(u'Utilisateur déjà banni, il faut en choisir un autre.')
else:
success = cheat(player['id'], target_id)
if success:
target = get_player(target_id)
ban(player['id'], target_id, True)
flash(u'Trop cool, %s a été tranché pour un bon moment.' \
% target['firstname'])
else:
ban(player['id'], target_id, False)
return banned()
# Liste des joueurs non bannis, triée dans l'ordre croissant ou décroissant
players = sorted(get_players_not_banned(), \
key=lambda player: player['firstname'], \
reverse = random.choice([True, False]))
# sans le joueur actuel
players = filter(lambda p: p['id'] != player['id'], players)
return render_template('play.html', players=players)
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('home'))
return render_template('login.html', error=error)
@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('home'))
if __name__ == '__main__':
app.run()

4
roulette.wsgi Normal file
View file

@ -0,0 +1,4 @@
import sys
sys.path.insert(0, '/var/www/roulette')
from roulette import app as application

23
schema.sql Normal file
View file

@ -0,0 +1,23 @@
drop table if exists players;
create table players (
id integer primary key autoincrement,
firstname text not null,
name text not null,
ban_end float not null
);
drop table if exists machines;
create table machines (
id integer primary key autoincrement,
player_id integer not null,
ip text not null
);
drop table if exists bans;
create table bans (
id integer primary key autoincrement,
player_id integer not null,
target_id integer not null,
success integer not null,
time float not null
);

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

BIN
static/img/k6lqsh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

BIN
static/img_red/k6lqsh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

188
static/style.css Normal file
View file

@ -0,0 +1,188 @@
body {
background: #3b5998;
color: white;
text-align: center;
font-family: helvetica, sans;
}
a {
text-decoration: none;
color: grey;
}
#body_bis {
margin-left: auto;
margin-right: auto;
width: 90%;
max-width: 1300px;
min-width: 550px;
}
#banner {
height: 101px;
background: center center url('img/asocial_metzwork_v1.png') no-repeat;
padding: 10px 10px;
}
#content_container {
margin-left: 200px;
margin-right: 200px;
position: relative;
background: white;
color: black;
padding-left: 12px;
padding-right: 12px;
padding-top: 10px;
padding-bottom: 10px;
}
#content_container div.corner {
height: 70px;
width: 70px;
background-repeat: no-repeat;
position: absolute;
}
#content_topleft {
top: 0px;
left: 0px;
background-image: url('img/corner_topleft.png');
background-color: #3b5998;
z-index: 1;
}
#content_topright {
top: 0px;
right: 0px;
background-image: url('img/corner_topright.png');
background-color: #3b5998;
z-index: 2;
}
#content_bottomleft {
bottom: 0px;
left: 0px;
background-image: url('img/corner_bottomleft.png');
background-color: #3b5998;
z-index: 3;
}
#content_bottomright {
bottom: 0px;
right: 0px;
background-image: url('img/corner_bottomright.png');
background-color: #3b5998;
z-index: 4;
}
#content {
position: relative;
z-index: 5;
text-align: left;
}
/* Contenu */
#content h1 {
margin-top: 5px;
padding-left: 50px;
font-size: 130%;
}
#content h2, #content p {
margin-left: 20px;
margin-right: 20px;
}
#content h2 {
margin-top:0px;
font-size: 100%;
margin-bottom: 5px;
padding-bottom: 0px;
}
#content p {
text-indent: 2em;
margin-top: 0px;
font-size: 90%;
}
#content ul#history {
padding-left: 60px;
}
#content li.events {
font-size: 180%;
}
#content li .normal {
color: black;
font-size: 80%;
}
#content li.ban {
color: red;
}
#content li.warn {
color: orange;
}
#content li.ok {
color: #1D1;
}
#content_container #footer {
margin-top: 4em;
font-size: 70%;
}
p#play {
text-align: center;
font-size: 1.5em;
font-weight: bold;
margin-top: 1.6em;
margin-bottom: 2em;
}
p#play a {
font-weight: bold;
color: white;
background: #2E2;
padding: .5em 2em;
border: 1px solid #191;
border-radius: 10px;
}
p#play a:hover {
background: #191;
}
form#select {
width: 500px;
margin: 1em auto;
background: #FA0;
border-radius: 1em;
border: 1px solid #C70;
padding: 1em 0em;
text-align: center;
}
ul.flashes {
font-size: 80%;
font-weight: bold;
color: #222;
list-style-type: none;
padding: 0;
width: 90%;
margin: .5em auto;
}
ul.flashes li {
background: #2E2;
border: 1px solid #191;
border-radius: .6em;
margin-bottom: 1em;
padding: .4em .7em;
}

188
static/style_red.css Normal file
View file

@ -0,0 +1,188 @@
body {
background: #3b5998;
color: red;
text-align: center;
font-family: helvetica, sans;
}
a {
text-decoration: none;
color: grey;
}
#body_bis {
margin-left: auto;
margin-right: auto;
width: 90%;
max-width: 1300px;
min-width: 550px;
}
#banner {
height: 101px;
background: center center url('img_red/asocial_metzwork_v1.png') no-repeat;
padding: 10px 10px;
}
#content_container {
margin-left: 200px;
margin-right: 200px;
position: relative;
background: red;
color: white;
padding-left: 12px;
padding-right: 12px;
padding-top: 10px;
padding-bottom: 10px;
}
#content_container div.corner {
height: 70px;
width: 70px;
background-repeat: no-repeat;
position: absolute;
}
#content_topleft {
top: 0px;
left: 0px;
background-image: url('img_red/corner_topleft.png');
background-color: #3b5998;
z-index: 1;
}
#content_topright {
top: 0px;
right: 0px;
background-image: url('img_red/corner_topright.png');
background-color: #3b5998;
z-index: 2;
}
#content_bottomleft {
bottom: 0px;
left: 0px;
background-image: url('img_red/corner_bottomleft.png');
background-color: #3b5998;
z-index: 3;
}
#content_bottomright {
bottom: 0px;
right: 0px;
background-image: url('img_red/corner_bottomright.png');
background-color: #3b5998;
z-index: 4;
}
#content {
position: relative;
z-index: 5;
text-align: left;
}
/* Contenu */
#content h1 {
margin-top: 5px;
padding-left: 50px;
font-size: 130%;
}
#content h2, #content p {
margin-left: 20px;
margin-right: 20px;
}
#content h2 {
margin-top:0px;
font-size: 100%;
margin-bottom: 5px;
padding-bottom: 0px;
}
#content p {
text-indent: 2em;
margin-top: 0px;
font-size: 90%;
}
#content ul#history {
padding-left: 60px;
}
#content li.events {
font-size: 180%;
}
#content li .normal {
color: black;
font-size: 80%;
}
#content li.ban {
color: red;
}
#content li.warn {
color: orange;
}
#content li.ok {
color: #1D1;
}
#content_container #footer {
margin-top: 4em;
font-size: 70%;
}
p#play {
text-align: center;
font-size: 1.5em;
font-weight: bold;
margin-top: 1.6em;
margin-bottom: 2em;
}
p#play a {
font-weight: bold;
color: white;
background: #2E2;
padding: .5em 2em;
border: 1px solid #191;
border-radius: 10px;
}
p#play a:hover {
background: #191;
}
form#select {
width: 500px;
margin: 1em auto;
background: #FA0;
border-radius: 1em;
border: 1px solid #C70;
padding: 1em 0em;
text-align: center;
}
ul.flashes {
font-size: 80%;
font-weight: bold;
color: #222;
list-style-type: none;
padding: 0;
width: 90%;
margin: .5em auto;
}
ul.flashes li {
background: #2E2;
border: 1px solid #191;
border-radius: .6em;
margin-bottom: 1em;
padding: .4em .7em;
}

16
templates/banned.html Normal file
View file

@ -0,0 +1,16 @@
{% extends "layout_banned.html" %}
{% block content %}
<h1>Tu es tranché</h1>
<p>
{{ explanation }}
</p>
<p>
Rétablissement de la connexion externe dans approximativement :
</p>
<p style="text-align: center">
<strong>{{ timeleft }}</strong>.
</p>
<p>
(redémarrer le navigateur peut être nécessaire une fois ce délai écoulé)
</p>
{% endblock %}

27
templates/home.html Normal file
View file

@ -0,0 +1,27 @@
{% extends "layout.html" %}
{% block content %}
<h1>24 heures</h1>
<h2>Toi aussi, joue au rézoman !</h2>
<p>
Jusqu'à samedi 11h, tu peux essayer de trancher tes
camarades pour 30 min.
</p>
<p id="play" style="text-align: center; font-size: 1.5em">
<a href="{{url_for('play')}}">Jouer</a>
</p>
<h2>Historique personnel</h2>
{% if bans_hist != [] %}
<ul id="history">
{% for entry in bans_hist %}
<li class="{{ entry[0] }}" class="events">
<span class="normal"> {{ entry[1] }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p>
Rien pour l'instant
</p>
{% endif %}
{% endblock %}

27
templates/home.html~ Normal file
View file

@ -0,0 +1,27 @@
{% extends "layout.html" %}
{% block content %}
<h1>24 heures</h1>
<h2>Toi aussi, joue au rézoman !</h2>
<p>
Jusqu'à mercredi 9h, tu peux essayer de trancher tes
camarades pour 30 min.
</p>
<p id="play" style="text-align: center; font-size: 1.5em">
<a href="{{url_for('play')}}">Jouer</a>
</p>
<h2>Historique personnel</h2>
{% if bans_hist != [] %}
<ul id="history">
{% for entry in bans_hist %}
<li class="{{ entry[0] }}" class="events">
<span class="normal"> {{ entry[1] }}</span>
</li>
{% endfor %}
</ul>
{% else %}
<p>
Rien pour l'instant
</p>
{% endif %}
{% endblock %}

43
templates/layout.html Normal file
View file

@ -0,0 +1,43 @@
<!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>
<title>asocial metzwork - 24h</title>
<meta http-equiv="content-type"
content="text/html;charset=utf-8" />
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
<link rel="shortcut icon" type="image/x-icon"
href="{{ url_for('static', filename='favicon.ico') }}" />
</head>
<body>
<div id="body_bis">
<div id="banner">
</div>
<div id="content_container">
<div id="content_topleft" class="corner">
</div>
<div id="content_topright" class="corner">
</div>
<div id="content">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<div id="content_bottomleft" class="corner">
</div>
<div id="content_bottomright" class="corner">
</div>
<div id="footer">contact technique : goulven.kermarec@supelec.fr</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!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>
<title>asocial metzwork - 24h</title>
<meta http-equiv="content-type"
content="text/html;charset=utf-8" />
<meta http-equiv="refresh" content="20">
<link rel="stylesheet" href="{{ url_for('static', filename='style_red.css') }}" />
<link rel="shortcut icon" type="image/x-icon"
href="{{ url_for('static', filename='favicon.ico') }}" />
</head>
<body>
<div id="body_bis">
<div id="banner">
</div>
<div id="content_container">
<div id="content_topleft" class="corner">
</div>
<div id="content_topright" class="corner">
</div>
<div id="content">
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<div id="content_bottomleft" class="corner">
</div>
<div id="content_bottomright" class="corner">
</div>
<div id="footer">contact technique : goulven.kermarec@supelec.fr</div>
</div>
</div>
</body>
</html>

15
templates/login.html Normal file
View file

@ -0,0 +1,15 @@
{% extends "layout.html" %}
{% block content %}
<h1>Login</h1>
{% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Identifiant :
<dd><input type=text name=username>
<dt>Mot de passe :
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
{% endblock %}

View file

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<h1>Vous n'êtes pas inscrit</h1>
<p>
Si vous désirez participer malgré les risques que le jeu comporte, veuillez
envoyer un email à goulven.kermarec@supelec.fr .
(Vous ne pouvez pas jouer depuis Supélec)
</p>
{% endblock %}

View file

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<h1>Vous n'êtes pas inscrits</h1>
<p>
Si vous désirez participer malgré les risques que le jeu comporte, veuillez
envoyer un email à goulven.kermarec@supelec.fr .
(Vous ne pouvez pas jouer depuis Supélec)
</p>
{% endblock %}

23
templates/play.html Normal file
View file

@ -0,0 +1,23 @@
{% extends "layout.html" %}
{% block content %}
<h1>24 heures</h1>
<h2>Trancher un joueur</h2>
<p>
Ta cible sera notifiée quelle que soit l'issue de ta tentative. Question
chance, c'est 50-50.
</p>
<form id="select" action="" method="post">
<select name="target_id">
<p><option value="none">(sélectionner un joueur)</option>
{% for player in players %}
<option value="{{ player.id }}">
{{ "%s %s" % (player.firstname, player.name) }}
</option></p>
{% endfor %}
</select>
<input type="submit" value="Tchak !" />
</form>
<p>
<a href="{{ url_for('home') }}">&laquo; retour à l'accueil</a>
</p>
{% endblock %}