Adding Teacher Panel to start/end event recording
parent
6b79de7866
commit
8aac49c38c
Binary file not shown.
149
server.py
149
server.py
|
@ -1,53 +1,150 @@
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify, session, render_template
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
|
from datetime import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
application = app
|
application = app
|
||||||
|
app.secret_key = os.urandom(24)
|
||||||
|
|
||||||
try:
|
#try:
|
||||||
from local_settings import *
|
# from local_settings import *
|
||||||
except ImportError:
|
#except ImportError:
|
||||||
print("Can't import from localsettings, terminating")
|
# print("Can't import from localsettings, terminating")
|
||||||
exit()
|
# exit()
|
||||||
|
|
||||||
EXPECTED_PASSWORD = APPLICATION_PASSWORD
|
EXPECTED_PASSWORD = "8V33zfvkImxBZcigtQ"
|
||||||
uri = MONGO_URI
|
#APPLICATION_PASSWORD
|
||||||
|
uri = "mongodb://zpdai.rkg.lv/submit"
|
||||||
|
#MONGO_URI
|
||||||
|
TEACHER_PASSWORD = "teacher_password_123"
|
||||||
|
|
||||||
mongo_client = MongoClient(uri)
|
mongo_client = MongoClient(uri)
|
||||||
db = mongo_client["user_interactions"]
|
db = mongo_client["user_interactions"]
|
||||||
collection = db["user_events"]
|
events_col = db["user_events"]
|
||||||
|
sessions_col = db["monitoring_sessions"]
|
||||||
|
|
||||||
|
current_session = {
|
||||||
|
'active': False,
|
||||||
|
'class_name': None,
|
||||||
|
'start_time': None,
|
||||||
|
'session_id': None
|
||||||
|
}
|
||||||
|
|
||||||
def validate_event(event):
|
def validate_event(event):
|
||||||
required_keys = ["@version", "type", "TimeStamp", "data", "@timestamp"]
|
required_keys = ["@version", "type", "TimeStamp", "data", "@timestamp"]
|
||||||
return all(key in event for key in required_keys)
|
return all(key in event for key in required_keys)
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def add_cors_headers(response):
|
||||||
|
response.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
|
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
|
||||||
|
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, X-Password'
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/api/session_status', methods=['OPTIONS'])
|
||||||
|
@app.route('/teacher/control', methods=['OPTIONS'])
|
||||||
|
def handle_options():
|
||||||
|
return jsonify(), 200, {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type'
|
||||||
|
}
|
||||||
|
|
||||||
@app.route('/submit', methods=['POST', 'OPTIONS'])
|
@app.route('/submit', methods=['POST', 'OPTIONS'])
|
||||||
def submit_event():
|
def submit_event():
|
||||||
if request.method == 'OPTIONS':
|
if request.method == 'OPTIONS':
|
||||||
resp = jsonify()
|
return jsonify(), 204
|
||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
|
||||||
resp.headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
|
|
||||||
resp.headers['Access-Control-Allow-Headers'] = 'Content-Type, X-Password'
|
|
||||||
return resp
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
password = request.headers.get('X-Password')
|
password = request.headers.get('X-Password')
|
||||||
if password != EXPECTED_PASSWORD:
|
if password != EXPECTED_PASSWORD:
|
||||||
return jsonify({"error": "Неверный пароль"}), 403
|
return jsonify({"error": "Неверный пароль"}), 403
|
||||||
|
|
||||||
event = request.get_json()
|
event = request.get_json()
|
||||||
|
|
||||||
if not event or not validate_event(event):
|
if not event or not validate_event(event):
|
||||||
resp = jsonify({"error": "Неверный формат"})
|
return jsonify({"error": "Неверный формат"}), 400
|
||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return resp, 400
|
|
||||||
|
|
||||||
collection.insert_one(event)
|
if current_session['active']:
|
||||||
|
event['session_id'] = current_session['session_id']
|
||||||
|
events_col.insert_one(event)
|
||||||
|
return jsonify({"message": "Действие сохранено"}), 200
|
||||||
|
else:
|
||||||
|
return jsonify({"error": "Сессия не активна"}), 400
|
||||||
|
|
||||||
resp = jsonify({"message": "Действие успешно сохранено"})
|
|
||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return resp, 200
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
resp = jsonify({"error": str(e)})
|
return jsonify({"error": str(e)}), 500
|
||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
|
||||||
return resp, 500
|
@app.route('/api/session_status', methods=['GET', 'OPTIONS'])
|
||||||
|
def session_status():
|
||||||
|
if request.method == 'OPTIONS':
|
||||||
|
return jsonify(), 204
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'active': current_session['active'],
|
||||||
|
'class_name': current_session['class_name'],
|
||||||
|
'session_id': current_session['session_id']
|
||||||
|
}), 200
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/teacher', methods=['GET'])
|
||||||
|
def teacher_panel():
|
||||||
|
if not session.get('teacher_logged_in'):
|
||||||
|
return render_template('login.html')
|
||||||
|
return render_template('teacher_panel.html')
|
||||||
|
|
||||||
|
@app.route('/teacher/login', methods=['POST', 'OPTIONS'])
|
||||||
|
def teacher_login():
|
||||||
|
if request.method == 'OPTIONS':
|
||||||
|
return jsonify(), 204
|
||||||
|
|
||||||
|
password = request.json.get('password')
|
||||||
|
if password == TEACHER_PASSWORD:
|
||||||
|
session['teacher_logged_in'] = True
|
||||||
|
return jsonify({'status': 'success'}), 200
|
||||||
|
return jsonify({'error': 'Invalid password'}), 401
|
||||||
|
|
||||||
|
@app.route('/teacher/control', methods=['POST', 'OPTIONS'])
|
||||||
|
def session_control():
|
||||||
|
if request.method == 'OPTIONS':
|
||||||
|
return jsonify(), 200
|
||||||
|
|
||||||
|
if not session.get('teacher_logged_in'):
|
||||||
|
return jsonify({'error': 'Unauthorized'}), 401
|
||||||
|
|
||||||
|
action = request.json.get('action')
|
||||||
|
class_name = request.json.get('class_name')
|
||||||
|
|
||||||
|
if action == 'start' and class_name:
|
||||||
|
current_session.update({
|
||||||
|
'active': True,
|
||||||
|
'class_name': class_name,
|
||||||
|
'start_time': datetime.now(),
|
||||||
|
'session_id': os.urandom(16).hex()
|
||||||
|
})
|
||||||
|
sessions_col.insert_one({
|
||||||
|
'session_id': current_session['session_id'],
|
||||||
|
'class': class_name,
|
||||||
|
'start_time': current_session['start_time'],
|
||||||
|
'end_time': None,
|
||||||
|
'active': True
|
||||||
|
})
|
||||||
|
return jsonify({'status': 'session_started'}), 200
|
||||||
|
|
||||||
|
elif action == 'stop':
|
||||||
|
sessions_col.update_one(
|
||||||
|
{'session_id': current_session['session_id']},
|
||||||
|
{'$set': {'active': False, 'end_time': datetime.now()}}
|
||||||
|
)
|
||||||
|
current_session.update({
|
||||||
|
'active': False,
|
||||||
|
'class_name': None,
|
||||||
|
'start_time': None,
|
||||||
|
'session_id': None
|
||||||
|
})
|
||||||
|
return jsonify({'status': 'session_stopped'}), 200
|
||||||
|
|
||||||
|
return jsonify({'error': 'Invalid request'}), 400
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Авторизация учителя</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 40px; }
|
||||||
|
form {
|
||||||
|
max-width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
input, button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form onsubmit="login(event)">
|
||||||
|
<h2>Вход для учителя</h2>
|
||||||
|
<input type="password" id="password" placeholder="Пароль" required>
|
||||||
|
<button type="submit">Войти</button>
|
||||||
|
<p id="error" style="color: red;"></p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function login(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
|
||||||
|
const response = await fetch('/teacher/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ password })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.href = '/teacher';
|
||||||
|
} else {
|
||||||
|
document.getElementById('error').textContent = 'Неверный пароль';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,72 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Панель учителя</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||||
|
.status { padding: 10px; margin: 10px 0; }
|
||||||
|
.active { background: #dff0d8; }
|
||||||
|
.inactive { background: #f2dede; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="status" class="status"></div>
|
||||||
|
<input type="text" id="className" placeholder="Название класса">
|
||||||
|
<button onclick="controlSession('start')">Начать сессию</button>
|
||||||
|
<button onclick="controlSession('stop')">Остановить сессию</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
async function controlSession(action) {
|
||||||
|
try {
|
||||||
|
const className = action === 'start'
|
||||||
|
? document.getElementById('className').value.trim()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (action === 'start' && !className) {
|
||||||
|
alert('Введите название класса!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/teacher/control', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Password': 'teacher_password_123'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: action,
|
||||||
|
class_name: className
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
alert(result.status || (action === 'start' ? 'Сессия начата' : 'Сессия остановлена'));
|
||||||
|
updateStatus();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка управления сессией:', error);
|
||||||
|
alert(`Ошибка: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateStatus() {
|
||||||
|
const response = await fetch('/api/session_status');
|
||||||
|
const status = await response.json();
|
||||||
|
const statusDiv = document.getElementById('status');
|
||||||
|
statusDiv.className = `status ${status.active ? 'active' : 'inactive'}`;
|
||||||
|
statusDiv.textContent = status.active
|
||||||
|
? `Активная сессия: ${status.class_name}`
|
||||||
|
: 'Сессия не активна';
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(updateStatus, 3000);
|
||||||
|
updateStatus();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue