diff --git a/TODO b/TODO index 145e2fca9b7947aba4b08ab6863ef5799e6baccc..8b7b2ec688e3a64016048d4cb7fbb343297cd054 100644 --- a/TODO +++ b/TODO @@ -7,3 +7,5 @@ další instance, které mají stejné id, ale jiné uuid?) - hooky na create/delete user - přehlednější log +- type=1 ignorovat (nestěžovat si, že je unknown) +- indexy diff --git a/app/flask b/app/flask new file mode 100755 index 0000000000000000000000000000000000000000..b567abf5a828def9c9bbf54d40dda7712cc6d946 --- /dev/null +++ b/app/flask @@ -0,0 +1,5 @@ +#!/bin/sh +export FLASK_APP=zoom.py +export FLASK_ENV=development +flask "$@" + diff --git a/app/templates/main.html b/app/templates/main.html new file mode 100644 index 0000000000000000000000000000000000000000..222421361bf87075af9b016f678d1f8fce0e42e1 --- /dev/null +++ b/app/templates/main.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<head> + <style> + #heading { + position: relative; + } + #schedule { + position: relative; + top: 2.5ex; + } + .room { + border: 1px solid green; + } + .hour { + border-bottom: 1px solid green; + } + .roomhead p { + text-align: center; + font-weight: bold; + margin-top: 0; + } + .meeting { + border: 1px solid blue; + font-size: smaller; + background-color: #ccccff; + } + + </style> +</head> +<body> + <h1>MFF Zoom</h1> + <form method=GET action="?"> + <label for=date>Date:</label> + <input id=date type=date name=date value="{{ g.date }}"> + <select name=hours> + <option value=0{{ " selected" if g.hours==0 else "" }}>Working hours</option> + <option value=1{{ " selected" if g.hours==1 else "" }}>Whole day</option> + </select> + <input type=submit name=submit value="Submit"> + </form> + + <h2>Schedule for {{ g.dow }} {{ g.date }}</h2> + + <div id=heading> +{% for r in g.rooms %} + <div class=roomhead style='position: absolute; left: {{ r.x }}px; top: 0px; width: {{ r.w }}px;'> + <p>{{ r.name }}</p> + </div> +{% endfor %} + </div> + <div id=schedule> +{% for r in g.rooms %} + <div class=room style='position: absolute; left: {{ r.x }}px; top: 0px; width: {{ r.w }}px; height: {{ r.h }}px;'></div> +{% endfor %} +{% for h in g.hours %} + <div class=hour style='position: absolute; left: {{ h.x }}px; top: {{ h.y }}px; width: {{ h.w }}px; height: {{ h.h }}px;'></div> +{% endfor %} +{% for m in g.meetings %} + <div class=meeting style='position: absolute; left: {{ m.x }}px; top: {{ m.y }}px; width: {{ m.w }}px; height: {{ m.h }}px;' title='{{ m.topic|e }}'> + {{ m.start }} – {{ m.end }} + </div> +{% endfor %} + </div> +</body> +</html> diff --git a/app/zoom.py b/app/zoom.py new file mode 100644 index 0000000000000000000000000000000000000000..c157106da3b729c86c69aae5f7734aad1aa2de96 --- /dev/null +++ b/app/zoom.py @@ -0,0 +1,137 @@ +import json +from flask import Flask, render_template, request, g +import psycopg2 +import psycopg2.extras +import time +import sys + +### Flask app object ### + +app = Flask(__name__) +app.config.from_pyfile('config.py') + +### Database connection ### + +db_connection = None +db = None + +# XXX: This is safe only because we never write to the database. Otherwise, we would +# have to handle transactions and roll them back if an exception occurs during +# processing of the request. + +def db_connect(): + global db_connection, db + db_connection = psycopg2.connect( + host = 'localhost', + user = app.config['DB_USER'], + password = app.config['DB_PASSWD'], + dbname = app.config['DB_NAME'], + ) + db = db_connection.cursor(cursor_factory=psycopg2.extras.NamedTupleCursor) + +def db_query(query, args=()): + if db is None: + db_connect() + try: + db.execute(query, args) + except psycopg2.DatabaseError: + # Reconnect if the connection died (timeout, server restart etc.) + db_connect() + db.execute(query, args) + +### Schedule ### + +def get_date(): + try: + d = request.args.get('date', "") + return time.strptime(d, "%Y-%m-%d") + except ValueError: + return time.localtime() + +rooms = [ + ('Z1', 'zoom-1@d3s.mff.cuni.cz'), + ('Z2', 'zoom-2@d3s.mff.cuni.cz'), + ('Z3', 'zoom-3@d3s.mff.cuni.cz'), + ('Z4', 'zoom-4@d3s.mff.cuni.cz'), + ('Z5', 'zoom-5@d3s.mff.cuni.cz'), + ('Z6', 'zoom-6@d3s.mff.cuni.cz'), + ('Z7', 'zoom-7@d3s.mff.cuni.cz'), + ('Z8', 'zoom-8@d3s.mff.cuni.cz'), +] + +@app.route('/') +def main_page(): + dt = get_date() + date = time.strftime("%Y-%m-%d", dt) + t = time.mktime(dt) + g.date = date + g.dow = time.strftime("%A", dt) + + hours_arg = request.args.get("hours", "") + if hours_arg in ["0", "1"]: + g.hours = int(hours_arg) + else: + g.hours = 0 + + if g.hours == 0: + hour_min = 8 + hour_max = 24 + else: + hour_min = 0 + hour_max = 24 + num_hours = hour_max - hour_min + + num_rooms = len(rooms) + email_to_room_index = { rooms[i][1]: i for i in range(num_rooms) } + room_box_width = 100 + room_hour_height = 50 + g.total_width = room_box_width * num_rooms + g.total_height = num_hours * room_hour_height + g.rooms = [{ + "x": i * room_box_width + 1, + "w": room_box_width - 1, + "h": g.total_height - 1, + "name": rooms[i][0], + } for i in range(num_rooms)] + + g.hours = [{ + "x": 1, + "y": i * room_hour_height + 1, + "w": num_rooms * room_box_width - 1, + "h": room_hour_height - 1, + } for i in range(num_hours)] + + # XXX: No meeting is ever longer than 24 hours + db_query(""" + SELECT m.meeting_id, m.topic, m.start_time, m.duration, u.email, u.full_name + FROM zoom_meetings m + JOIN zoom_users u ON u.id = m.host_id + WHERE start_time >= DATE %s - INTERVAL '1 day' + AND start_time < DATE %s + INTERVAL '1 day' + """, + (date, date)) + + g.meetings = [] + for r in db.fetchall(): + i = email_to_room_index.get(r.email, -1) + if i < 0: + continue + start_t = int(r.start_time.timestamp()) + end_t = start_t + r.duration*60 + rel_start = start_t - t - hour_min*3600 + rel_end = rel_start + r.duration*60 + start = max(0, int(rel_start)) + end = min(num_hours * 3600, int(rel_end)) + app.logger.debug("Meeting: %s start=%s end=%s room_i=%s", r, start, end, i) + if start < end: + g.meetings.append({ + "x": i * room_box_width + 4, + "y": int(start / 3600. * room_hour_height), + "w": room_box_width - 7, + "h": int((end - start) / 3600 * room_hour_height), + "start": time.strftime("%H:%M", time.localtime(start_t)), + "end": time.strftime("%H:%M", time.localtime(end_t)), + "topic": r.topic, + }) + + return render_template('main.html') diff --git a/requirements.txt b/requirements.txt index 699adcf9e2a3e72aaec9dedad79eb3c176472681..6e41d4050c28b722c2a96e4f228b8d3d1ccfc835 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ zoomus psycopg2 python-dateutil +Flask