added confused dashboard

This commit is contained in:
2025-05-13 04:03:43 +02:00
parent d4d7f485a7
commit b86f0a1e14
5 changed files with 109 additions and 7 deletions

View File

@@ -1,9 +1,11 @@
from flask import Flask from flask import Flask
from .routes import main from flask_socketio import SocketIO
from .models import db from .models import db
from .utils import init_password_generator, init_serializer from .utils import init_password_generator, init_serializer
from .auth import init_auth from .auth import init_auth
socketio = SocketIO()
def create_app(): def create_app():
app = Flask(__name__, static_folder='static', static_url_path='/static') app = Flask(__name__, static_folder='static', static_url_path='/static')
app.config.from_object('config.Config') app.config.from_object('config.Config')
@@ -16,5 +18,8 @@ def create_app():
init_serializer(app) init_serializer(app)
init_auth(app) init_auth(app)
socketio.init_app(app)
from .routes import main
app.register_blueprint(main) app.register_blueprint(main)
return app return app

View File

@@ -4,6 +4,8 @@ from datetime import datetime
from typing import List, cast from typing import List, cast
import flask_login import flask_login
from flask_socketio import join_room
import pytz import pytz
from flask import ( from flask import (
Blueprint, Blueprint,
@@ -19,6 +21,8 @@ from flask import (
url_for, url_for,
) )
from . import socketio
from .auth import check_password, user_loader from .auth import check_password, user_loader
from .forms import DeckForm, InfoBannerForm, LoginForm, PasswordForm from .forms import DeckForm, InfoBannerForm, LoginForm, PasswordForm
from .models import Deck, InfoBanner, Password, db from .models import Deck, InfoBanner, Password, db
@@ -40,6 +44,10 @@ main = Blueprint('main', __name__)
slidev_deck_pattern = re.compile(r"^\/slides\/[^\/]+\/\d*(?:\?.*)?$") slidev_deck_pattern = re.compile(r"^\/slides\/[^\/]+\/\d*(?:\?.*)?$")
slidev_all_pattern = re.compile(r"^\/slides\/[^\/]+(?:\/presenter)?\/\d+(?:\?.*)?$") slidev_all_pattern = re.compile(r"^\/slides\/[^\/]+(?:\/presenter)?\/\d+(?:\?.*)?$")
@socketio.on("connect")
def on_connect():
if flask_login.current_user.is_authenticated:
join_room("instructor")
@main.before_request @main.before_request
def ensure_cookie(): def ensure_cookie():
@@ -56,7 +64,7 @@ def ensure_cookie():
@main.after_request @main.after_request
def inject_confused_button(response): def inject_confused_button(response):
if True: if True:
if (slidev_deck_pattern.match(request.path) or request.path == '/')and response.content_type.startswith('text/html'): if (slidev_deck_pattern.match(request.path) or request.path == '/') and response.content_type.startswith('text/html') and not flask_login.current_user.is_authenticated :
if response.direct_passthrough: if response.direct_passthrough:
response.direct_passthrough = False response.direct_passthrough = False
body = response.get_data(as_text=True) body = response.get_data(as_text=True)
@@ -157,8 +165,16 @@ def access_link(access_code: str):
@main.route('/confused', methods=['POST']) @main.route('/confused', methods=['POST'])
def confused(): def confused():
resp = jsonify(success=True)
return resp socketio.emit(
"confused",
{
"time": datetime.utcnow().isoformat()
},
to="instructor"
)
return ("", 204)
@main.route('/retrieve-token', methods=['POST']) @main.route('/retrieve-token', methods=['POST'])
def set_username_token(): def set_username_token():
@@ -293,3 +309,7 @@ def hide_active_banner():
db.session.commit() db.session.commit()
return redirect(url_for('main.banner')) return redirect(url_for('main.banner'))
@main.route('/admin/confused')
@flask_login.login_required
def confused_notifications():
return render_template("confused.html")

View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Live Confusion Alerts</title>
<!-- Tailwind DEV CDN (for production, use your compiled CSS) -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.min.js"></script>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="max-w-3xl mx-auto mt-12 px-4">
<h1 class="text-3xl font-bold mb-6">Live Confusion Alerts</h1>
<div id="notif-list" class="space-y-4 text-gray-700">
<p class="italic text-gray-500">Waiting for students to click “Im confused…”</p>
</div>
</div>
<!-- Socket.IO client (served by your Flask-SocketIO) -->
<script>
const notifList = document.getElementById("notif-list");
const socket = io();
socket.on("confused", data => {
// Remove placeholder if its still there
if (notifList.children.length === 1 &&
notifList.children[0].classList.contains("italic")) {
notifList.innerHTML = "";
}
// Build a new alert box
const box = document.createElement("div");
box.className = [
"flex items-start",
"bg-red-50 border border-red-400",
"text-red-800 px-4 py-3 rounded-lg shadow",
"relative"
].join(" ");
// Message text
const who = data.who || "Someone";
const now = new Date().toLocaleTimeString();
box.innerHTML = `
<div class="flex-1">
<strong class="font-semibold">Confused!</strong>
<p>${who} clicked “Im confused.” at ${now}</p>
</div>
`;
// Close button
const btn = document.createElement("button");
btn.className = "ml-4 text-red-600 hover:text-red-900 focus:outline-none";
btn.setAttribute("aria-label", "Dismiss");
btn.innerHTML = "&times;";
btn.onclick = () => box.remove();
box.appendChild(btn);
// Prepend new alert
notifList.prepend(box);
// Autoremove after 20 s
setTimeout(() => box.remove(), 20000);
});
</script>
</body>
</html>

View File

@@ -29,8 +29,14 @@
<!-- Flowbite --> <!-- Flowbite -->
<link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.css" rel="stylesheet" />
<!-- in base.html, in <head> or just before </body> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.1/socket.io.min.js"></script>
</head> </head>
<body class="bg-background text-text min-h-screen font-sans"> <body class="bg-background text-text min-h-screen font-sans">
<!-- Header --> <!-- Header -->
@@ -129,6 +135,7 @@
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
<script> <script>
function enableUsernameEdit() { function enableUsernameEdit() {
@@ -144,5 +151,5 @@
saveBtn.classList.remove('hidden'); saveBtn.classList.remove('hidden');
} }
</script> </script>
</body> </body>
</html> </html>

4
run.py
View File

@@ -1,6 +1,6 @@
from app import create_app from app import create_app, socketio
app = create_app() app = create_app()
if __name__ == '__main__': if __name__ == '__main__':
app.run() socketio.run(app, port=5002, debug=True)