summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomek Dubrownik <t.dubrownik@gmail.com>2012-01-29 02:34:11 +0100
committerTomek Dubrownik <t.dubrownik@gmail.com>2012-01-29 02:34:11 +0100
commit6912034ced9d7856722814bb61ebb51159630dd2 (patch)
tree2cc5021243fd40f0f7e62b24814ab7afbee7b7b4
parentf9696225a224625d610bbd599ed4ba07409b33e5 (diff)
downloadcheckinator-6912034ced9d7856722814bb61ebb51159630dd2.tar.gz
checkinator-6912034ced9d7856722814bb61ebb51159630dd2.tar.bz2
checkinator-6912034ced9d7856722814bb61ebb51159630dd2.zip
basic user panel
-rw-r--r--at.py121
-rw-r--r--config.py4
-rw-r--r--static/css/basic.css17
-rw-r--r--templates/account.html55
-rw-r--r--templates/basic.html24
-rw-r--r--templates/claim.html38
-rw-r--r--templates/main.html46
-rw-r--r--templates/post_claim.html7
-rw-r--r--templates/register.html45
9 files changed, 259 insertions, 98 deletions
diff --git a/at.py b/at.py
index 21e4cc6..e702228 100644
--- a/at.py
+++ b/at.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
-import logging
import sqlite3
import threading
import traceback
import json
-from flask import Flask, render_template, abort, redirect, session, request, flash, g
+from flask import Flask, render_template, abort, g, \
+ redirect, session, request, flash, url_for
from datetime import datetime
from wsgiref import simple_server
from pesto import Response, dispatcher_app
@@ -18,8 +18,6 @@ import config
app = Flask('at')
app.secret_key = config.secret_key
-logger = logging.getLogger()
-conn = None
updater = None
from functools import wraps
@@ -42,26 +40,32 @@ def req_to_ctx():
def strfts(ts, format='%d/%m/%Y %H:%M'):
return datetime.fromtimestamp(ts).strftime(format)
-def setup_db():
+@app.before_request
+def make_connection():
conn = sqlite3.connect(config.db)
conn.row_factory = sqlite3.Row
conn.isolation_level = None # for autocommit mode
- return conn
+ g.db = conn
-DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'owner', 'ignored'])
-User = namedtuple('User', ['login', 'passwd', 'url'])
+@app.teardown_request
+def close_connection(exception):
+ g.db.close()
+
+DeviceInfo = namedtuple('DeviceInfo', ['hwaddr', 'name', 'owner', 'ignored'])
+User = namedtuple('User', ['id', 'login', 'passwd', 'url'])
def get_device_info(conn, hwaddr):
return list(get_device_infos(conn, (hwaddrs,)))[0]
def get_device_infos(conn, hwaddrs):
- stmt = '''select hwaddr, name, ignored, login, url from
+ stmt = '''select hwaddr, name, ignored, userid, login, url from
devices left join users on devices.owner = users.userid
where devices.hwaddr in (''' + ','.join(['?'] * len(hwaddrs)) + ')'
for row in conn.execute(stmt, hwaddrs):
- owner = User(row['login'], None, row['url']) if row['login'] else None
+ owner = User(row['userid'], row['login'], None, row['url']) \
+ if row['login'] else None
ignored = row['ignored']
- yield DeviceInfo(row['hwaddr'], owner, ignored)
+ yield DeviceInfo(row['hwaddr'], row['name'], owner, ignored)
class Updater(threading.Thread):
def __init__(self, timeout, *a, **kw):
@@ -91,7 +95,7 @@ class Updater(threading.Thread):
self.lock.acquire()
self.active[hwaddr] = (atime, ip, name)
self.lock.release()
- logger.info('updated %s with atime %s and ip %s',
+ app.logger.info('updated %s with atime %s and ip %s',
hwaddr, strfts(atime), ip)
class CapUpdater(Updater):
@@ -102,15 +106,15 @@ class CapUpdater(Updater):
while True:
try:
with open(self.cap_file, 'r', buffering=0) as f:
- logger.info('Updater ready on cap file %s', self.cap_file)
+ app.logger.info('Updater ready on cap file %s', self.cap_file)
while True:
hwaddr = f.readline().strip()
if not hwaddr:
break
self.update(hwaddr)
- logging.warning('Cap file %s closed, reopening', self.cap_file)
+ app.logger.warning('Cap file %s closed, reopening', self.cap_file)
except Exception as e:
- logging.error('Updater got an exception:\n' + \
+ app.logger.error('Updater got an exception:\n' + \
traceback.format_exc(e))
sleep(10.0)
@@ -126,7 +130,7 @@ class DnsmasqUpdater(Updater):
try:
mtime = os.stat(self.lease_file).st_mtime
if mtime > self.last_modified:
- logger.info('Lease file changed, updating')
+ app.logger.info('Lease file changed, updating')
with open(self.lease_file, 'r') as f:
for line in f:
ts, hwaddr, ip, name, client_id = line.split(' ')
@@ -134,7 +138,7 @@ class DnsmasqUpdater(Updater):
self.last_modified = mtime
sleep(3.0)
except Exception as e:
- logging.error('Updater got an exception:\n' + \
+ app.logger.error('Updater got an exception:\n' + \
traceback.format_exc(e))
sleep(10.0)
@@ -154,18 +158,17 @@ def list_all():
}
result['users'] = map(prettify_user, result['users'])
result['unknown'] = len(result['unknown'])
- del result['login']
return json.dumps(result)
def now_at():
devices = updater.get_active_devices()
- device_infos = list(get_device_infos(conn, devices.keys()))
+ device_infos = list(get_device_infos(g.db, devices.keys()))
device_infos.sort(key=lambda di: devices.__getitem__)
users = list(dict((info.owner, devices[info.hwaddr][0]) for info in device_infos
if info.owner and not info.ignored).iteritems())
users.sort(key=lambda (u, a): a, reverse=True)
unknown = set(devices.keys()) - set(d.hwaddr for d in device_infos)
- return dict(users=users, unknown=unknown, login=session.get('login'))
+ return dict(users=users, unknown=unknown)
restrict_to_hs = restrict_ip(prefix=config.claimable_prefix,
exclude=config.claimable_exclude)
@@ -178,12 +181,12 @@ def register_form():
@app.route('/register', methods=['POST'])
@restrict_to_hs
def register():
- login = request.form['login']
+ login = request.form['login'].lower()
url = request.form['url']
if 'wiki' in request.form:
url = config.wiki_url % { 'login': login }
try:
- conn.execute('insert into users (login, url, pass) values (?, ?, ?)',
+ g.db.execute('insert into users (login, url, pass) values (?, ?, ?)',
[login, url, sha256(request.form['password']).hexdigest()])
return redirect('/')
except sqlite3.Error as e:
@@ -195,21 +198,22 @@ def register():
def login_form():
return render_template('login.html', **req_to_ctx())
-def get_credentials(login, password):
- row = conn.execute('select userid from users where login = ? and pass = ?',
- [login, sha256(password).hexdigest()]).fetchone()
- return row and row['userid']
+def get_user(conn, login, password):
+ row = conn.execute('select userid, login, pass, url from users where\
+ login = ? and pass = ?', [login, sha256(password).hexdigest()]).fetchone()
+ return row and User(row['userid'], row['login'], None, row['url'])
@app.route('/login', methods=['POST'])
@restrict_to_hs
def login():
- login = request.form.get('login', '')
+ login = request.form.get('login', '').lower()
pwd = request.form.get('password', '')
goto = request.values.get('goto') or '/'
- userid = get_credentials(login, pwd)
- if userid:
- session['userid'] = userid
- session['login'] = login
+ user = get_user(g.db, login, pwd)
+ if user:
+ session['userid'] = user.id
+ session['login'] = user.login
+ session['user'] = user
return redirect(goto)
else:
flash('Username or password invalid', category='error')
@@ -236,8 +240,7 @@ def login_required(f):
@login_required
def claim_form():
hwaddr, name = updater.get_device(request.remote_addr)
- return render_template('claim.html', hwaddr=hwaddr, name=name,
- login=session['login'])
+ return render_template('claim.html', hwaddr=hwaddr, name=name)
@app.route('/claim', methods=['POST'])
@restrict_to_hs
@@ -250,18 +253,60 @@ def claim():
else:
userid = session['userid']
try:
- conn.execute('insert into devices (hwaddr, name, owner, ignored)\
+ g.db.execute('insert into devices (hwaddr, name, owner, ignored)\
values (?, ?, ?, ?)', [hwaddr, request.form['name'], userid, False])
ctx = {}
except sqlite3.Error as e:
ctx = { 'error': 'Could not add device! Perhaps someone claimed it?' }
return render_template('post_claim.html', **ctx)
+def get_user_devices(conn, user):
+ devs = conn.execute('select hwaddr, name, ignored from devices where\
+ owner = ?', [user.id])
+ return (DeviceInfo(row['hwaddr'], row['name'], user, row['ignored']) for
+ row in devs)
+
+def set_password(conn, user, password):
+ return conn.execute('update users set pass = ? where userid = ?',
+ [sha256(password).hexdigest(), user.id])
+
+@app.route('/account', methods=['GET','POST'])
+@login_required
+def account():
+ if request.method == 'POST':
+ old = request.form['old']
+ if get_user(g.db, session['login'], old) and \
+ set_password(g.db, session['user'], request.form['new']):
+ flash('Password changed', category='message')
+ else:
+ flash('Could not change password!', category='error')
+ devices = get_user_devices(g.db, session['user'])
+ return render_template('account.html', devices=devices)
+
+def set_ignored(conn, hwaddr, user, value):
+ return conn.execute('update devices set ignored = ? where hwaddr = ? and owner = ?',
+ [value, hwaddr, user.id])
+
+def delete_device(conn, hwaddr, user):
+ return conn.execute('delete from devices where hwaddr = ? and owner = ?',
+ [hwaddr, user.id])
+
+@app.route('/devices/<id>/<action>/')
+@login_required
+def device(id, action):
+ user = session['user']
+ if action == 'hide':
+ set_ignored(g.db, id, user, True)
+ if action == 'show':
+ set_ignored(g.db, id, user, False)
+ if action == 'delete':
+ delete_device(g.db, id, user)
+ return redirect(url_for('account'))
+
port = 8080
if __name__ == '__main__':
- logger.setLevel(logging.DEBUG)
- logger.addHandler(logging.StreamHandler())
- conn = setup_db()
+ import logging
+ app.logger.setLevel(logging.DEBUG)
updater = DnsmasqUpdater(config.lease_file, config.lease_offset, config.timeout)
updater.start()
- app.run('0.0.0.0', config.port)
+ app.run('0.0.0.0', config.port, debug=config.debug)
diff --git a/config.py b/config.py
index e89b78c..67f9b04 100644
--- a/config.py
+++ b/config.py
@@ -1,3 +1,5 @@
+debug = True
+
port = 8080
db = './at.db'
@@ -8,7 +10,7 @@ timeout = 3000
wiki_url = 'http://hackerspace.pl/wiki/doku.php?id=people:%(login)s:start'
-claimable_prefix = '192.168.1.'
+claimable_prefix = '' #'192.168.1.'
claimable_exclude = [
# '127.0.0.1',
]
diff --git a/static/css/basic.css b/static/css/basic.css
new file mode 100644
index 0000000..d347512
--- /dev/null
+++ b/static/css/basic.css
@@ -0,0 +1,17 @@
+p.error {
+ color: red
+}
+p.message {
+ color: green
+}
+table.devices, .devices td, .devices th {
+ padding: .5em;
+ border: 1px solid black;
+ border-collapse: collapse;
+}
+td.invisible {
+ background-color: red
+}
+td.visible {
+ background-color: green
+}
diff --git a/templates/account.html b/templates/account.html
new file mode 100644
index 0000000..91cd668
--- /dev/null
+++ b/templates/account.html
@@ -0,0 +1,55 @@
+{% extends "basic.html" %}
+{% block content %}
+<a href="/">Back to homepage</a>
+<h2>Account settings</h2>
+{% for msg in get_flashed_messages(True) %}
+ <p class="{{ msg[0] }}">{{ msg[1] }}</p>
+{% endfor %}
+<h3>Change password</h3>
+<form action="" method="POST">
+ <table>
+ <label><tr>
+ <td>Current password:</td>
+ <td><input type="password" name="old"></td>
+ </tr></label>
+ <label><tr>
+ <td>New password:</td>
+ <td><input type="password" name="new"></td>
+ </tr></label>
+ <label><tr>
+ <td>Confirm new password:</td>
+ <td><input type="password" name="new2"></td>
+ </tr></label>
+ <label><tr>
+ <td></td>
+ <td><input type="submit" value="Save"></td>
+ </tr></label>
+ </table>
+</form>
+<h3>Claimed devices</h3>
+<table class="devices">
+ <tr>
+ <th>MAC</th>
+ <th>Device name</th>
+ <th>Visible</th>
+ <th>Toggle visibility</th>
+ <th>Delete</th>
+ </tr>
+{% for device in devices %}
+ <tr>
+ <td>{{ device.hwaddr }}</td>
+ <td>{{ device.name }}</td>
+ {% if device.ignored %}
+ <td class="invisible">invisible</td>
+ <td><a href="devices/{{ device.hwaddr }}/show">make visible</a></td>
+ {% else %}
+ <td class="visible">visible</td>
+ <td><a href="devices/{{ device.hwaddr }}/hide">make invisible</a></td>
+ {% endif %}
+ <td><a href="devices/{{ device.hwaddr }}/delete">delete device</a></td>
+ </tr>
+ </tbody>
+{% endfor%}
+</table>
+<p><a href="/claim">claim this device</a>
+{% endblock %}
diff --git a/templates/basic.html b/templates/basic.html
new file mode 100644
index 0000000..2ff8808
--- /dev/null
+++ b/templates/basic.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+ <head>
+ {% block head %}
+ <link rel="stylesheet" type="text/css" href="/static/css/basic.css">
+ <title>{% block title %}Now at hackerspace{% endblock %}</title>
+ {% endblock %}
+ </head>
+ <body>
+ {% block body %}
+ <div class="login">
+ {% if session.login %}
+ logged in as {{ session.login }} |
+ <a href="account">account</a> |
+ <a href="logout">log out</a>
+ {% else %}
+ <a href="login">login</a> | <a href="register">register</a>
+ {% endif %}
+ </div>
+ {% block content %}
+ {% endblock %}
+ {% endblock %}
+ </body>
+</html>
diff --git a/templates/claim.html b/templates/claim.html
index f86a3fb..413b7bc 100644
--- a/templates/claim.html
+++ b/templates/claim.html
@@ -1,17 +1,21 @@
-<html>
-<body>
-<h2>Claiming a device</h2>
-{% if not hwaddr %}
-<p class="error">Unknown MAC. Are you sure you're in the hackerspace?</p>
-{% else %}
-You are about to claim <strong>{{ hwaddr }}</strong> as <strong>{{ login }}</strong>. Do you wish to continue?
-<table>
-<form action="" method="post">
-<label><tr><td>Device name (optional):</td><td><input type="text" name="name" value="{{ name }}"></td></tr></label>
-<tr><td><input type="submit" value="yes"></td>
-</form>
-<td><a href="/"><button>no</button></button></td>
-</table>
-{% endif %}
-</body>
-</html>
+{% extends "basic.html" %}
+{% block content %}
+ <h2>Claiming a device</h2>
+ {% if not hwaddr %}
+ <p class="error">Unknown MAC. Are you sure you're in the hackerspace?</p>
+ {% else %}
+ You are about to claim <strong>{{ hwaddr }}</strong> as <strong>{{ session.login }}</strong>. Do you wish to continue?
+ <table>
+ <form action="" method="post">
+ <label><tr>
+ <td>Device name (optional):</td>
+ <td><input type="text" name="name" value="{{ name }}"></td>
+ </tr></label>
+ <tr>
+ <td><input type="submit" value="yes"></td>
+ </form>
+ <td><a href="/"><button>no</button></button></td>
+ </tr>
+ </table>
+ {% endif %}
+{% endblock %}
diff --git a/templates/main.html b/templates/main.html
index 038afd7..198b4d0 100644
--- a/templates/main.html
+++ b/templates/main.html
@@ -1,24 +1,22 @@
-<html>
-<body>
-<h2>Now at hackerspace!</h2>
-<div class="login">
-{% if login %}
-logged in as {{ login }} | <a href="logout">log out</a>
-{% else %}
-<a href="login">login</a> | <a href="register">register</a>
-{% endif %}
-</div>
-Recently at <a href="http://www.hackerspace.pl">hackerspace</a>:
-<ul>
-{% for user, timestamp in users %}
-<li>
-{% if user.url %}<a href="{{ user.url }}">{% endif %}
-{{ user.login }} ({{ timestamp|strfts() }})
-{% if user.url %}</a>{% endif %}
-</li>
-{% endfor %}
-</ul>
-<hr>
-There are {{ unknown|length }} unknown devices operating. <a href="claim">Claim this device!</a>
-</body>
-</html>
+{% extends "basic.html" %}
+{% block title %}
+Now at hackerspace
+{% endblock %}
+{% block content %}
+ <h2>Now at hackerspace!</h2>
+ Recently at <a href="http://www.hackerspace.pl">hackerspace</a>:
+ <ul>
+ {% for user, timestamp in users %}
+ <li>
+ {% if user.url %}
+ <a href="{{ user.url }}">{% endif %}
+ {{ user.login }} ({{ timestamp|strfts() }})
+ {% if user.url %}</a>
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ <hr>
+ There are {{ unknown|length }} unknown devices operating.
+ <a href="claim">Claim this device!</a>
+{% endblock %}
diff --git a/templates/post_claim.html b/templates/post_claim.html
index 981eae2..858323b 100644
--- a/templates/post_claim.html
+++ b/templates/post_claim.html
@@ -1,5 +1,5 @@
-<html>
-<body>
+{% extends "basic.html" %}
+{% block content %}
{% if error %}
<h2>Error!</h2>
<p class="error">{{ error }}</p>
@@ -8,5 +8,4 @@
Congratulations, you just claimed this device!
<a href="/">go back</a>
{% endif %}
-</body>
-</html>
+{% endblock %}
diff --git a/templates/register.html b/templates/register.html
index b3ba570..9c6399f 100644
--- a/templates/register.html
+++ b/templates/register.html
@@ -1,19 +1,36 @@
-<html>
-<body>
-<h2>Register a new account
+{% extends "basic.html" %}
+{% block content %}
+<h2>Register a new account</h2>
{% for error in get_flashed_messages() %}
<p class="error">{{ error }}</p>
{% endfor %}
<form action="" method="POST">
-<table>
-<label><tr><td>login</td><td><input type="text" name="login" value="{{ login }}"></td></tr></label>
-<label><tr><td>password</td><td><input type="password" name="password"></td></tr></label>
-<label><tr><td>confirm password</td><td><input type="password" name="password2"></td></tr></label>
-<label><tr><td>homepage url</td><td><input type="text" name="url" value="{{ url }}"></td></tr></label>
-<label><tr><td>use wiki page as url</td><td><input type="checkbox" name="wiki"
- value="yes" {% if wiki == 'yes' %}checked="yes"{% endif %}></td></tr</label>
-<tr><td></td><td><input type="submit" value="register"></input></td></tr>
-</table>
+ <table>
+ <label><tr>
+ <td>login</td>
+ <td><input type="text" name="login" value="{{ login }}"></td>
+ </tr></label>
+ <label><tr>
+ <td>password</td>
+ <td><input type="password" name="password"></td>
+ </tr></label>
+ <label><tr>
+ <td>confirm password</td>
+ <td><input type="password" name="password2"></td>
+ </tr></label>
+ <label><tr>
+ <td>homepage url</td>
+ <td><input type="text" name="url" value="{{ url }}"></td>
+ </tr></label>
+ <label><tr>
+ <td>use wiki page as url</td>
+ <td><input type="checkbox" name="wiki"
+ value="yes" {% if wiki == 'yes' %}checked="yes"{% endif %}></td>
+ </tr</label>
+ <tr>
+ <td></td>
+ <td><input type="submit" value="register"></input></td>
+ </tr>
+ </table>
</form>
-</body>
-</html>
+{% endblock %}