Debug monitoring
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 8 Feb 2018 17:30:46 +0000 (18:30 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 8 Feb 2018 17:30:46 +0000 (18:30 +0100)
bin/www
entities/assessment.js
models/assessment.js
public/javascripts/assessment.js
public/javascripts/monitor.js
public/javascripts/utils/socketMessages.js
public/javascripts/utils/validation.js
sockets.js

diff --git a/bin/www b/bin/www
index c964949..eacc6e5 100755 (executable)
--- a/bin/www
+++ b/bin/www
@@ -4,28 +4,28 @@
  * Module dependencies.
  */
 
  * 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.
  */
 
 
 /**
  * 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.
  */
 
 app.set('port', port);
 
 /**
  * Create HTTP server.
  */
 
-let server = http.createServer(app);
+const server = http.createServer(app);
 
 /*
  * CRON tasks
  */
 
 
 /*
  * 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();
 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);
 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
 
 /*
  * Sockets handling
@@ -53,7 +52,7 @@ require('../sockets')(io);
 
 function normalizePort(val)
 {
 
 function normalizePort(val)
 {
-  let port = parseInt(val, 10);
+  const port = parseInt(val, 10);
 
   if (isNaN(port)) // named pipe
     return val;
 
   if (isNaN(port)) // named pipe
     return val;
@@ -99,8 +98,8 @@ function onError(error)
 
 function onListening()
 {
 
 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);
     ? 'pipe ' + addr
     : 'port ' + addr.port;
   console.log('Listening on ' + bind);
index b4e3092..3dc1952 100644 (file)
@@ -27,6 +27,8 @@ const AssessmentEntity =
         *     number: student number
         *     inputs: array of indexed arrays of integers (or html text if not quiz)
         *     startTime, endTime
         *     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
         */
 
         *     password: random string identifying student for exam session TEMPORARY
         */
 
@@ -134,6 +136,8 @@ const AssessmentEntity =
                                startTime: Date.now(),
                                endTime: undefined,
                                password: password,
                                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
                        }}},
                                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)
        {
 
        hasInput: function(aid, number, password, idx, cb)
        {
index 9ab92ba..d5bd3a1 100644 (file)
@@ -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) => {
        endSession: function(aid, number, password, cb)
        {
                AssessmentEntity.endAssessment(aid, number, password, (err,ret) => {
index aced6f2..ad309d7 100644 (file)
@@ -157,7 +157,7 @@ let V = new Vue({
                                                // Got password: students answers locked to this page until potential teacher
                                                // action (power failure, computer down, ...)
                                        }
                                                // 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);
                                                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.stage = 4;
                                this.answers.showSolution = true;
+                               this.answers.displayAll = true;
                                return;
                        }
                        $.ajax("/end/assessment", {
                                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
                                        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) {
                                },
                        });
                },
                // stage 3 --> 4 (on socket message "feedback")
                setAnswers: function(m) {
-                       for (let i=0; i<m.answers.length; i++)
-                               assessment.questions[i].answer = m.answers[i];
+                       const answers = JSON.parse(m.answers);
+                       for (let i=0; i<answers.length; i++)
+                               assessment.questions[i].answer = answers[i];
                        this.answers.showSolution = true;
                        this.answers.showSolution = true;
+                       this.answers.displayAll = true;
                        this.stage = 4;
                        this.stage = 4;
+                       socket.disconnect();
+                       socket = null;
                },
        },
 });
                },
        },
 });
index 07a0814..23972a9 100644 (file)
@@ -105,7 +105,7 @@ new Vue({
                                                        });
                                                }
                                                // TODO: notations not coherent (input / answer... when, which ?)
                                                        });
                                                }
                                                // TODO: notations not coherent (input / answer... when, which ?)
-                                               this.assessment.papers[paperIdx].inputs.push(m.answer); //input+index
+                                               this.assessment.papers[paperIdx].inputs.push(JSON.parse(m.answer)); //input+index
                                        });
                                },
                        });
                                        });
                                },
                        });
@@ -114,7 +114,7 @@ new Vue({
                        // In the end, send answers to students
                        socket.emit(
                                message.allAnswers,
                        // In the end, send answers to students
                        socket.emit(
                                message.allAnswers,
-                               { answers: this.assessment.questions.map( q => { return q.answer; }) }
+                               { answers: JSON.stringify(this.assessment.questions.map( q => { return q.answer; })) }
                        );
                },
        },
                        );
                },
        },
index 4ffd05b..7c86fd8 100644 (file)
@@ -1,10 +1,13 @@
 // Socket message list, easier life in case of a message renaming
 
 let message = {
 // 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",
        newAnswer: "new answer",
-       // receive all answers to an exam (server --> student)
+       // Receive all answers to an exam (monitor --> server --> student)
        allAnswers: "all answers",
        allAnswers: "all answers",
+       // Next 2 to monitor students disconnections
+       studentConnect: "student connect",
+       studentDisconnect: "student disconnect",
 };
 
 try { module.exports = message; } catch (err) {} //for server
 };
 
 try { module.exports = message; } catch (err) {} //for server
index 560bd5c..aa39ebe 100644 (file)
@@ -26,6 +26,9 @@ Validator.Paper = {
        "inputs": Validator.Input,
        "startTime": "positiveInteger",
        "endTime": "positiveInteger",
        "inputs": Validator.Input,
        "startTime": "positiveInteger",
        "endTime": "positiveInteger",
+       "discoTime": "positiveInteger",
+       "discoCount": "positiveInteger",
+       "totalDisco": "positiveInteger",
        "password": "password",
 };
 
        "password": "password",
 };
 
index 2ce8ae9..9c230be 100644 (file)
@@ -1,23 +1,20 @@
 const message = require("./public/javascripts/utils/socketMessages");
 const params = require("./config/parameters");
 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)
 {
 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;
                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)
                {
                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.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
                        });
                }
                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...
                        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});
                                });
                        });
                }
                                });
                        });
                }