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 |