Commit 1699423aa0ac21473f0346b5dff77ee9f588a04e
1 parent
b339e748
Exists in
master
and in
1 other branch
- show browser focus/unfocus events in /admin
- disabled content-security-policy because of MathJax - removed column from tables in /admin to simplify
Showing
9 changed files
with
79 additions
and
49 deletions
Show diff stats
BUGS.md
| @@ -22,6 +22,7 @@ | @@ -22,6 +22,7 @@ | ||
| 22 | 22 | ||
| 23 | # FIXED | 23 | # FIXED |
| 24 | 24 | ||
| 25 | +- server nao esta a receber eventos focus/blur dos utilizadores diferentes de '0', estranho... | ||
| 25 | - permitir adicionar imagens nas perguntas. | 26 | - permitir adicionar imagens nas perguntas. |
| 26 | - detect_unfocus.js so funciona se estiver inline no html. porquê??? | 27 | - detect_unfocus.js so funciona se estiver inline no html. porquê??? |
| 27 | - inserir novo aluno /admin não fecha. | 28 | - inserir novo aluno /admin não fecha. |
app.py
| @@ -218,7 +218,8 @@ class App(object): | @@ -218,7 +218,8 @@ class App(object): | ||
| 218 | 'password_defined': pw != '', | 218 | 'password_defined': pw != '', |
| 219 | 'grades': self.get_student_grades_from_test(uid, self.testfactory['ref']), | 219 | 'grades': self.get_student_grades_from_test(uid, self.testfactory['ref']), |
| 220 | 'ip_address': self.online.get(uid, {}).get('student', {}).get('ip_address',''), | 220 | 'ip_address': self.online.get(uid, {}).get('student', {}).get('ip_address',''), |
| 221 | - 'user_agent': self.online.get(uid, {}).get('student', {}).get('user_agent','') | 221 | + 'user_agent': self.online.get(uid, {}).get('student', {}).get('user_agent',''), |
| 222 | + 'focus': self.online.get(uid, {}).get('student', {}).get('focus', True), | ||
| 222 | }) | 223 | }) |
| 223 | return l | 224 | return l |
| 224 | 225 | ||
| @@ -263,3 +264,6 @@ class App(object): | @@ -263,3 +264,6 @@ class App(object): | ||
| 263 | logger.error('Insert failed: student {} already exists.'.format(uid)) | 264 | logger.error('Insert failed: student {} already exists.'.format(uid)) |
| 264 | else: | 265 | else: |
| 265 | logger.info('New student inserted into database: {}, {}'.format(uid, name)) | 266 | logger.info('New student inserted into database: {}, {}'.format(uid, name)) |
| 267 | + | ||
| 268 | + def set_student_focus(self, uid, value): | ||
| 269 | + self.online[uid]['student']['focus'] = value |
serve.py
| @@ -63,7 +63,8 @@ def secureheaders(): | @@ -63,7 +63,8 @@ def secureheaders(): | ||
| 63 | headers = cherrypy.response.headers | 63 | headers = cherrypy.response.headers |
| 64 | headers['X-Frame-Options'] = 'DENY' | 64 | headers['X-Frame-Options'] = 'DENY' |
| 65 | headers['X-XSS-Protection'] = '1; mode=block' | 65 | headers['X-XSS-Protection'] = '1; mode=block' |
| 66 | - headers['Content-Security-Policy'] = "default-src='self'" | 66 | + # FIXME disabled because MathJax requires unsafe javascript eval: |
| 67 | + # headers['Content-Security-Policy'] = "default-src 'self'" | ||
| 67 | if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None): | 68 | if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None): |
| 68 | headers['Strict-Transport-Security'] = 'max-age=31536000' # one year | 69 | headers['Strict-Transport-Security'] = 'max-age=31536000' # one year |
| 69 | 70 | ||
| @@ -88,6 +89,7 @@ class AdminWebService(object): | @@ -88,6 +89,7 @@ class AdminWebService(object): | ||
| 88 | } | 89 | } |
| 89 | return json.dumps(data, default=str) | 90 | return json.dumps(data, default=str) |
| 90 | 91 | ||
| 92 | + @cherrypy.tools.accept(media='application/json') # FIXME | ||
| 91 | def POST(self, **args): | 93 | def POST(self, **args): |
| 92 | # print('POST', args) # FIXME | 94 | # print('POST', args) # FIXME |
| 93 | if args['cmd'] == 'allow': | 95 | if args['cmd'] == 'allow': |
| @@ -106,6 +108,25 @@ class AdminWebService(object): | @@ -106,6 +108,25 @@ class AdminWebService(object): | ||
| 106 | print(args) | 108 | print(args) |
| 107 | 109 | ||
| 108 | # ============================================================================ | 110 | # ============================================================================ |
| 111 | +# Student webservice | ||
| 112 | +# ============================================================================ | ||
| 113 | +class StudentWebService(object): | ||
| 114 | + exposed = True | ||
| 115 | + _cp_config = { | ||
| 116 | + 'auth.require': [] | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + def __init__(self, app): | ||
| 120 | + self.app = app | ||
| 121 | + | ||
| 122 | + @cherrypy.tools.accept(media='application/json') # FIXME | ||
| 123 | + def POST(self, **args): | ||
| 124 | + uid = cherrypy.session.get(SESSION_KEY) | ||
| 125 | + if args['cmd'] == 'focus': | ||
| 126 | + v = json.loads(args['value']) | ||
| 127 | + self.app.set_student_focus(uid=args['number'], value=v) | ||
| 128 | + | ||
| 129 | +# ============================================================================ | ||
| 109 | # Webserver root | 130 | # Webserver root |
| 110 | # ============================================================================ | 131 | # ============================================================================ |
| 111 | class Root(object): | 132 | class Root(object): |
| @@ -287,6 +308,7 @@ if __name__ == '__main__': | @@ -287,6 +308,7 @@ if __name__ == '__main__': | ||
| 287 | # --- create webserver | 308 | # --- create webserver |
| 288 | webapp = Root(app) | 309 | webapp = Root(app) |
| 289 | webapp.adminwebservice = AdminWebService(app) | 310 | webapp.adminwebservice = AdminWebService(app) |
| 311 | + webapp.studentwebservice = StudentWebService(app) | ||
| 290 | 312 | ||
| 291 | # --- site wide configuration (valid for all apps) | 313 | # --- site wide configuration (valid for all apps) |
| 292 | cherrypy.tools.secureheaders = cherrypy.Tool('before_finalize', secureheaders, priority=60) | 314 | cherrypy.tools.secureheaders = cherrypy.Tool('before_finalize', secureheaders, priority=60) |
| @@ -312,6 +334,11 @@ if __name__ == '__main__': | @@ -312,6 +334,11 @@ if __name__ == '__main__': | ||
| 312 | 'tools.response_headers.on': True, | 334 | 'tools.response_headers.on': True, |
| 313 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], | 335 | 'tools.response_headers.headers': [('Content-Type', 'text/plain')], |
| 314 | }, | 336 | }, |
| 337 | + '/studentwebservice': { | ||
| 338 | + 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), | ||
| 339 | + 'tools.response_headers.on': True, | ||
| 340 | + 'tools.response_headers.headers': [('Content-Type', 'text/plain')], | ||
| 341 | + }, | ||
| 315 | '/static': { | 342 | '/static': { |
| 316 | 'tools.auth.on': False, # everything in /static is public | 343 | 'tools.auth.on': False, # everything in /static is public |
| 317 | 'tools.staticdir.on': True, | 344 | 'tools.staticdir.on': True, |
| @@ -0,0 +1,18 @@ | @@ -0,0 +1,18 @@ | ||
| 1 | +/* Fixes navigation panel overlaying content */ | ||
| 2 | +body { | ||
| 3 | + padding-top: 80px; | ||
| 4 | + background: #aaa; | ||
| 5 | +} | ||
| 6 | +/* Hack to avoid name clash between pygments and mathjax */ | ||
| 7 | +.MathJax .mo, | ||
| 8 | +.MathJax .mi { | ||
| 9 | + color: inherit; | ||
| 10 | +} | ||
| 11 | +.drop-shadow { | ||
| 12 | + -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, .5); | ||
| 13 | + box-shadow: 0px 2px 10px 3px rgba(0, 0, 0, .2); | ||
| 14 | + border-radius:5px; | ||
| 15 | +} | ||
| 16 | +textarea { | ||
| 17 | + font-family: monospace !important; | ||
| 18 | +} |
static/js/admin.js
| @@ -57,23 +57,24 @@ $(document).ready(function() { | @@ -57,23 +57,24 @@ $(document).ready(function() { | ||
| 57 | var active = []; | 57 | var active = []; |
| 58 | $.each(students, function(i, r) { | 58 | $.each(students, function(i, r) { |
| 59 | if (r['start_time'] != '') { | 59 | if (r['start_time'] != '') { |
| 60 | - active.push([r['uid'], r['name'], r['start_time'], r['ip_address'], r['user_agent']]); | 60 | + active.push([r['uid'], r['name'], r['start_time'], r['ip_address'], r['user_agent'], r['focus']]); |
| 61 | } | 61 | } |
| 62 | }); | 62 | }); |
| 63 | // sort by start time | 63 | // sort by start time |
| 64 | active.sort(function(a,b){return a[2] < b[2] ? -1 : (a[2] == b[2] ? 0 : 1);}); | 64 | active.sort(function(a,b){return a[2] < b[2] ? -1 : (a[2] == b[2] ? 0 : 1);}); |
| 65 | n = active.length; | 65 | n = active.length; |
| 66 | for(var i = 0; i < n; i++) { | 66 | for(var i = 0; i < n; i++) { |
| 67 | - rows += "<tr>\ | ||
| 68 | - <td>" + active[i][0] + "</td>\ | ||
| 69 | - <td>" + active[i][1] + "</td>\ | ||
| 70 | - <td>" + active[i][2].slice(0,10) + "</td>\ | ||
| 71 | - <td>" + active[i][2].slice(11,19) + '</td>\ | ||
| 72 | - <td><div data-toggle="tooltip" data-placement="top" title="' + active[i][4] + '">' + active[i][3] + '</div></td>\ | 67 | + rows += '<tr' + (active[i][5]? '' : ' class="danger"') + '>\ |
| 68 | + <td>' + active[i][0] + '</td>\ | ||
| 69 | + <td>' + active[i][1] + '</td>\ | ||
| 70 | + <td>' + active[i][2].slice(11,19) + '</td>\ | ||
| 71 | + <td><div data-toggle="tooltip" data-placement="top" title="'+active[i][4]+'">' + active[i][3] + '</div></td>\ | ||
| 72 | + <td>' + (active[i][5]? '' : '<span class="label label-danger">unfocus</span>') + '</td>\ | ||
| 73 | </tr>'; | 73 | </tr>'; |
| 74 | } | 74 | } |
| 75 | $("#online_students").html(rows); | 75 | $("#online_students").html(rows); |
| 76 | $("#online-header").html(n + " Activo(s)"); | 76 | $("#online-header").html(n + " Activo(s)"); |
| 77 | + | ||
| 77 | } | 78 | } |
| 78 | 79 | ||
| 79 | // ---------------------------------------------------------------------- | 80 | // ---------------------------------------------------------------------- |
| @@ -111,14 +112,12 @@ $(document).ready(function() { | @@ -111,14 +112,12 @@ $(document).ready(function() { | ||
| 111 | rows += '<tr id="' + uid + '" + class="">'; | 112 | rows += '<tr id="' + uid + '" + class="">'; |
| 112 | 113 | ||
| 113 | rows += '\ | 114 | rows += '\ |
| 114 | - <td><input type="checkbox" name="' + uid + '" value="true"' + (d['allowed'] ? 'checked' : '') + '></td>\ | ||
| 115 | - <td>' + uid + '</td>\ | ||
| 116 | - <td>' + d['name'] + (d['password_defined'] ? ' <span class="label label-default">pw</span>' : '') +'</td>\ | ||
| 117 | - <td>' + | ||
| 118 | - | 115 | + <td><input type="checkbox" name="' + uid + '" value="true"' + (d['allowed'] ? 'checked' : '') + '>' + |
| 116 | + (d['start_time']=='' ? '' : ' <span class="label label-success">teste</span>') + | ||
| 119 | // (d['online'] ? '<span class="label label-warning">online</span>' : '') + | 117 | // (d['online'] ? '<span class="label label-warning">online</span>' : '') + |
| 120 | - (d['start_time']==''?'':'<span class="label label-success">teste</span>') + | ||
| 121 | '</td>\ | 118 | '</td>\ |
| 119 | + <td>' + uid + '</td>\ | ||
| 120 | + <td>' + d['name'] + (d['password_defined'] ? ' <span class="label label-default">pw</span>' : '') +'</td>\ | ||
| 122 | <td>'; | 121 | <td>'; |
| 123 | var g = d['grades']; | 122 | var g = d['grades']; |
| 124 | var glength = g.length; | 123 | var glength = g.length; |
static/js/detect_unfocus.js
| @@ -2,10 +2,11 @@ $(document).ready(function() { | @@ -2,10 +2,11 @@ $(document).ready(function() { | ||
| 2 | $(window).focus(function(){ | 2 | $(window).focus(function(){ |
| 3 | $.ajax({ | 3 | $.ajax({ |
| 4 | type: "POST", | 4 | type: "POST", |
| 5 | - url: "/adminwebservice", | 5 | + url: "/studentwebservice", |
| 6 | data: { | 6 | data: { |
| 7 | "cmd": "focus", | 7 | "cmd": "focus", |
| 8 | - "name": $("#number").text() | 8 | + "number": $("#number").text(), |
| 9 | + "value": true | ||
| 9 | } | 10 | } |
| 10 | }); | 11 | }); |
| 11 | }); | 12 | }); |
| @@ -13,10 +14,11 @@ $(document).ready(function() { | @@ -13,10 +14,11 @@ $(document).ready(function() { | ||
| 13 | $(window).blur(function(e){ | 14 | $(window).blur(function(e){ |
| 14 | $.ajax({ | 15 | $.ajax({ |
| 15 | type: "POST", | 16 | type: "POST", |
| 16 | - url: "/adminwebservice", | 17 | + url: "/studentwebservice", |
| 17 | data: { | 18 | data: { |
| 18 | - "cmd": "blur", | ||
| 19 | - "name": $("#number").text() | 19 | + "cmd": "focus", |
| 20 | + "number": $("#number").text(), | ||
| 21 | + "value": false | ||
| 20 | } | 22 | } |
| 21 | }); | 23 | }); |
| 22 | }); | 24 | }); |
templates/admin.html
| @@ -90,9 +90,10 @@ | @@ -90,9 +90,10 @@ | ||
| 90 | <tr> | 90 | <tr> |
| 91 | <th>Número</th> | 91 | <th>Número</th> |
| 92 | <th>Nome</th> | 92 | <th>Nome</th> |
| 93 | - <th>Data de início</th> | 93 | + <!-- <th>Data de início</th> --> |
| 94 | <th>Hora de início</th> | 94 | <th>Hora de início</th> |
| 95 | <th>IP</th> | 95 | <th>IP</th> |
| 96 | + <th>Estado</th> | ||
| 96 | </tr> | 97 | </tr> |
| 97 | </thead> | 98 | </thead> |
| 98 | <tbody id="online_students"> | 99 | <tbody id="online_students"> |
| @@ -113,7 +114,7 @@ | @@ -113,7 +114,7 @@ | ||
| 113 | <th>Perm.</th> | 114 | <th>Perm.</th> |
| 114 | <th>Número</th> | 115 | <th>Número</th> |
| 115 | <th>Nome</th> | 116 | <th>Nome</th> |
| 116 | - <th>Estado</th> | 117 | + <!-- <th>Estado</th> --> |
| 117 | <th>Nota</th> | 118 | <th>Nota</th> |
| 118 | </tr> | 119 | </tr> |
| 119 | </thead> | 120 | </thead> |
templates/test.html
| @@ -10,18 +10,19 @@ | @@ -10,18 +10,19 @@ | ||
| 10 | <!-- MathJax --> | 10 | <!-- MathJax --> |
| 11 | <script type="text/x-mathjax-config"> | 11 | <script type="text/x-mathjax-config"> |
| 12 | MathJax.Hub.Config({ | 12 | MathJax.Hub.Config({ |
| 13 | - tex2jax: {inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]]} | 13 | + tex2jax: { |
| 14 | + inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]] | ||
| 15 | + } | ||
| 14 | }); | 16 | }); |
| 15 | </script> | 17 | </script> |
| 16 | - | ||
| 17 | - <script type="text/javascript" src="/static/js/mathjax/MathJax.js?config=TeX-AMS_CHTML-full"> | ||
| 18 | - </script> | 18 | + <script type="text/javascript" src="/static/js/mathjax/MathJax.js?config=TeX-AMS_CHTML-full"></script> |
| 19 | 19 | ||
| 20 | <!-- Bootstrap --> | 20 | <!-- Bootstrap --> |
| 21 | <link rel="stylesheet" href="/static/css/bootstrap.min.css"> | 21 | <link rel="stylesheet" href="/static/css/bootstrap.min.css"> |
| 22 | <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <!-- optional --> | 22 | <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <!-- optional --> |
| 23 | <link rel="stylesheet" href="/static/css/github.css"> <!-- syntax highlight --> | 23 | <link rel="stylesheet" href="/static/css/github.css"> <!-- syntax highlight --> |
| 24 | <link rel="stylesheet" href="/static/css/sticky-footer-navbar.css"> | 24 | <link rel="stylesheet" href="/static/css/sticky-footer-navbar.css"> |
| 25 | + <link rel="stylesheet" href="/static/css/test.css"> | ||
| 25 | 26 | ||
| 26 | <script src="/static/js/jquery.min.js"></script> | 27 | <script src="/static/js/jquery.min.js"></script> |
| 27 | <script src="/static/js/bootstrap.min.js"></script> | 28 | <script src="/static/js/bootstrap.min.js"></script> |
| @@ -32,26 +33,6 @@ | @@ -32,26 +33,6 @@ | ||
| 32 | <script src="/static/js/tabkey_in_textarea.js"></script> | 33 | <script src="/static/js/tabkey_in_textarea.js"></script> |
| 33 | <script src="/static/js/detect_unfocus.js"></script> | 34 | <script src="/static/js/detect_unfocus.js"></script> |
| 34 | 35 | ||
| 35 | - <style> | ||
| 36 | - /* Fixes navigation panel overlaying content */ | ||
| 37 | - body { | ||
| 38 | - padding-top: 80px; | ||
| 39 | - background: #aaa; | ||
| 40 | - } | ||
| 41 | - /* Hack to avoid name clash between pygments and mathjax */ | ||
| 42 | - .MathJax .mo, | ||
| 43 | - .MathJax .mi { | ||
| 44 | - color: inherit; | ||
| 45 | - } | ||
| 46 | - .drop-shadow { | ||
| 47 | - -webkit-box-shadow: 0 0 5px 2px rgba(0, 0, 0, .5); | ||
| 48 | - box-shadow: 0px 2px 10px 3px rgba(0, 0, 0, .2); | ||
| 49 | - border-radius:5px; | ||
| 50 | - } | ||
| 51 | - textarea { | ||
| 52 | - font-family: monospace !important; | ||
| 53 | - } | ||
| 54 | - </style> | ||
| 55 | </head> | 36 | </head> |
| 56 | <!-- ===================================================================== --> | 37 | <!-- ===================================================================== --> |
| 57 | <body> | 38 | <body> |
| @@ -59,7 +40,6 @@ | @@ -59,7 +40,6 @@ | ||
| 59 | <div class="container-fluid drop-shadow"> | 40 | <div class="container-fluid drop-shadow"> |
| 60 | <div class="navbar-header"> | 41 | <div class="navbar-header"> |
| 61 | <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar"> | 42 | <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar"> |
| 62 | - <!-- <span class="glyphicon glyphicon-menu-hamburger"></span> --> | ||
| 63 | <span class="icon-bar"></span> | 43 | <span class="icon-bar"></span> |
| 64 | <span class="icon-bar"></span> | 44 | <span class="icon-bar"></span> |
| 65 | <span class="icon-bar"></span> | 45 | <span class="icon-bar"></span> |
| @@ -85,7 +65,6 @@ | @@ -85,7 +65,6 @@ | ||
| 85 | <li><a href="/results">Ver resultados</a></li> | 65 | <li><a href="/results">Ver resultados</a></li> |
| 86 | % endif | 66 | % endif |
| 87 | <li><a data-toggle="modal" data-target="#sair" id="form-button-submit"><span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> Sair</a></li> | 67 | <li><a data-toggle="modal" data-target="#sair" id="form-button-submit"><span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> Sair</a></li> |
| 88 | - <!-- <li><a href="#">Change password</a></li> --> | ||
| 89 | </ul> | 68 | </ul> |
| 90 | </li> | 69 | </li> |
| 91 | </ul> | 70 | </ul> |
tools.py
| @@ -22,7 +22,6 @@ def load_yaml(filename, default=None): | @@ -22,7 +22,6 @@ def load_yaml(filename, default=None): | ||
| 22 | try: | 22 | try: |
| 23 | return yaml.load(f) | 23 | return yaml.load(f) |
| 24 | except yaml.YAMLError as e: | 24 | except yaml.YAMLError as e: |
| 25 | - # except yaml.parser.ParserError: | ||
| 26 | mark = e.problem_mark | 25 | mark = e.problem_mark |
| 27 | logger.error('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1)) | 26 | logger.error('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1)) |
| 28 | return default | 27 | return default |