From 29c8b391bcdf6ffca53545178e2ad194287a1bdc Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 8 Feb 2018 18:30:46 +0100 Subject: [PATCH] Debug monitoring --- bin/www | 21 +++++----- entities/assessment.js | 45 ++++++++++++++++++++++ models/assessment.js | 12 ++++++ public/javascripts/assessment.js | 13 ++++--- public/javascripts/monitor.js | 4 +- public/javascripts/utils/socketMessages.js | 7 +++- public/javascripts/utils/validation.js | 3 ++ sockets.js | 23 ++++++----- 8 files changed, 98 insertions(+), 30 deletions(-) diff --git a/bin/www b/bin/www index c964949..eacc6e5 100755 --- a/bin/www +++ b/bin/www @@ -4,28 +4,28 @@ * Module dependencies. */ -var app = require('../app'); -var http = require('http'); +const app = require('../app'); +const http = require('http'); /** * Get port from environment and store in Express. */ -let port = normalizePort(process.env.PORT || '3000'); +const port = normalizePort(process.env.PORT || '3000'); app.set('port', port); /** * Create HTTP server. */ -let server = http.createServer(app); +const server = http.createServer(app); /* * CRON tasks */ -var cron = require('node-cron'); -var UserModel = require("../models/user"); +const cron = require('node-cron'); +const UserModel = require("../models/user"); cron.schedule('0 0 0 * * *', function() { // Remove unlogged users every 24h UserModel.cleanUsersDb(); @@ -38,8 +38,7 @@ cron.schedule('0 0 0 * * *', function() { server.listen(port); server.on('error', onError); server.on('listening', onListening); -let io = require('socket.io').listen(server); //sockets too -//https://stackoverflow.com/a/24610678/4640434 +const io = require('socket.io').listen(server); /* * Sockets handling @@ -53,7 +52,7 @@ require('../sockets')(io); function normalizePort(val) { - let port = parseInt(val, 10); + const port = parseInt(val, 10); if (isNaN(port)) // named pipe return val; @@ -99,8 +98,8 @@ function onError(error) function onListening() { - let addr = server.address(); - let bind = typeof addr === 'string' + const addr = server.address(); + const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; console.log('Listening on ' + bind); diff --git a/entities/assessment.js b/entities/assessment.js index b4e3092..3dc1952 100644 --- a/entities/assessment.js +++ b/entities/assessment.js @@ -27,6 +27,8 @@ const AssessmentEntity = * number: student number * inputs: array of indexed arrays of integers (or html text if not quiz) * startTime, endTime + * discoTime, totalDisco: last disconnect timestamp (if relevant) + total + * discoCount: total disconnections * password: random string identifying student for exam session TEMPORARY */ @@ -134,6 +136,8 @@ const AssessmentEntity = startTime: Date.now(), endTime: undefined, password: password, + totalDisco: 0, + discoCount: 0, inputs: [ ], //TODO: this is stage 1, stack indexed answers. // then build JSON tree for easier access / correct }}}, @@ -141,6 +145,47 @@ const AssessmentEntity = ); }, + // NOTE: no callbacks for 2 next functions, failures are not so important + // (because monitored: teachers can see what's going on) + + addDisco: function(aid, number, deltaTime) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + }, + { $inc: { + "papers.$.discoCount": 1, + "papers.$.totalDisco": deltaTime, + } }, + { $set: { "papers.$.discoTime": null } } + ); + }, + + setDiscoTime: function(aid, number) + { + db.assessments.update( + { + _id: aid, + "papers.number": number, + }, + { $set: { "papers.$.discoTime": Date.now() } } + ); + }, + + getDiscoTime: function(aid, number, cb) + { + db.assessments.findOne( + { _id: aid }, + (err,a) => { + if (!!err) + return cb(err, null); + const idx = a.papers.findIndex( item => { return item.number == number; }); + cb(null, a.papers[idx].discoTime); + } + ); + }, hasInput: function(aid, number, password, idx, cb) { diff --git a/models/assessment.js b/models/assessment.js index 9ab92ba..d5bd3a1 100644 --- a/models/assessment.js +++ b/models/assessment.js @@ -115,6 +115,18 @@ const AssessmentModel = }); }, + // NOTE: no callbacks for 2 next functions, failures are not so important + // (because monitored: teachers can see what's going on) + + newConnection: function(aid, number) + { + //increment discoCount, reset discoTime to NULL, update totalDisco + AssessmentEntity.getDiscoTime(aid, number, (err,discoTime) => { + if (!!discoTime) + AssessmentEntity.addDisco(aid, number, discoTime - Date.now()); + }); + }, + endSession: function(aid, number, password, cb) { AssessmentEntity.endAssessment(aid, number, password, (err,ret) => { diff --git a/public/javascripts/assessment.js b/public/javascripts/assessment.js index aced6f2..ad309d7 100644 --- a/public/javascripts/assessment.js +++ b/public/javascripts/assessment.js @@ -157,7 +157,7 @@ let V = new Vue({ // Got password: students answers locked to this page until potential teacher // action (power failure, computer down, ...) } - socket = io.connect("/" + assessment.name, { + socket = io.connect("/", { query: "aid=" + assessment._id + "&number=" + this.student.number + "&password=" + this.student.password }); socket.on(message.allAnswers, this.setAnswers); @@ -232,6 +232,7 @@ let V = new Vue({ { this.stage = 4; this.answers.showSolution = true; + this.answers.displayAll = true; return; } $.ajax("/end/assessment", { @@ -248,17 +249,19 @@ let V = new Vue({ assessment.conclusion = ret.conclusion; this.stage = 3; delete this.student["password"]; //unable to send new answers now - socket.disconnect(); - socket = null; }, }); }, // stage 3 --> 4 (on socket message "feedback") setAnswers: function(m) { - for (let i=0; i { return q.answer; }) } + { answers: JSON.stringify(this.assessment.questions.map( q => { return q.answer; })) } ); }, }, diff --git a/public/javascripts/utils/socketMessages.js b/public/javascripts/utils/socketMessages.js index 4ffd05b..7c86fd8 100644 --- a/public/javascripts/utils/socketMessages.js +++ b/public/javascripts/utils/socketMessages.js @@ -1,10 +1,13 @@ // Socket message list, easier life in case of a message renaming let message = { - // send answer (student --> server --> monitor) + // Send answer (student --> server --> monitor) newAnswer: "new answer", - // receive all answers to an exam (server --> student) + // Receive all answers to an exam (monitor --> server --> student) allAnswers: "all answers", + // Next 2 to monitor students disconnections + studentConnect: "student connect", + studentDisconnect: "student disconnect", }; try { module.exports = message; } catch (err) {} //for server diff --git a/public/javascripts/utils/validation.js b/public/javascripts/utils/validation.js index 560bd5c..aa39ebe 100644 --- a/public/javascripts/utils/validation.js +++ b/public/javascripts/utils/validation.js @@ -26,6 +26,9 @@ Validator.Paper = { "inputs": Validator.Input, "startTime": "positiveInteger", "endTime": "positiveInteger", + "discoTime": "positiveInteger", + "discoCount": "positiveInteger", + "totalDisco": "positiveInteger", "password": "password", }; diff --git a/sockets.js b/sockets.js index 2ce8ae9..9c230be 100644 --- a/sockets.js +++ b/sockets.js @@ -1,23 +1,20 @@ const message = require("./public/javascripts/utils/socketMessages"); const params = require("./config/parameters"); +const AssessmentEntity = require("./entities/assessment"); const AssessmentModel = require("./models/assessment"); const ObjectId = require("bson-objectid"); module.exports = function(io) { - io.of("/").on("connection", socket => { + io.of("/").on("connection", socket => { //student or monitor connexion const aid = socket.handshake.query.aid; - socket.join(aid); - // Student or monitor connexion const isTeacher = !!socket.handshake.query.secret && socket.handshake.query.secret == params.secret; if (isTeacher) { - socket.on(message.newAnswer, m => { //got answer from student - socket.emit(message.newAnswer, m); - }); + socket.join(aid + "_teacher"); socket.on(message.allAnswers, m => { //send feedback to student (answers) - socket.broadcast.to(aid).emit(message.allAnswers, m); + socket.broadcast.to(aid + "_student").emit(message.allAnswers, m); }); } else //student @@ -27,9 +24,15 @@ module.exports = function(io) AssessmentModel.checkPassword(ObjectId(aid), number, password, (err,ret) => { if (!!err || !ret) return; //wrong password, or some unexpected error... - socket.on("disconnect", () => { - //TODO: notify monitor (grey low opacity background) - //Also send to server: discoTime in assessment.papers ... + AssessmentModel.newConnection(ObjectId(aid), number); + socket.broadcast.to(aid + "_teacher").emit(message.studentConnect, {number: number}); + socket.join(aid + "_student"); + socket.on(message.newAnswer, m => { //got answer from student client + socket.broadcast.to(aid + "_teacher").emit(message.newAnswer, m); + }); + socket.on("disconnect", () => { //notify monitor + server + AssessmentEntity.setDiscoTime(ObjectId(aid), number); + socket.broadcast.to(aid + "_teacher").emit(message.studentDisconnect, {number: number}); }); }); } -- 2.44.0