Commit 1699423aa0ac21473f0346b5dff77ee9f588a04e

Authored by Miguel Barão
1 parent b339e748
Exists in master and in 1 other branch dev

- show browser focus/unfocus events in /admin

- disabled content-security-policy because of MathJax
- removed column from tables in /admin to simplify
BUGS.md
... ... @@ -22,6 +22,7 @@
22 22  
23 23 # FIXED
24 24  
  25 +- server nao esta a receber eventos focus/blur dos utilizadores diferentes de '0', estranho...
25 26 - permitir adicionar imagens nas perguntas.
26 27 - detect_unfocus.js so funciona se estiver inline no html. porquê???
27 28 - inserir novo aluno /admin não fecha.
... ...
app.py
... ... @@ -218,7 +218,8 @@ class App(object):
218 218 'password_defined': pw != '',
219 219 'grades': self.get_student_grades_from_test(uid, self.testfactory['ref']),
220 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 224 return l
224 225  
... ... @@ -263,3 +264,6 @@ class App(object):
263 264 logger.error('Insert failed: student {} already exists.'.format(uid))
264 265 else:
265 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 63 headers = cherrypy.response.headers
64 64 headers['X-Frame-Options'] = 'DENY'
65 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 68 if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None):
68 69 headers['Strict-Transport-Security'] = 'max-age=31536000' # one year
69 70  
... ... @@ -88,6 +89,7 @@ class AdminWebService(object):
88 89 }
89 90 return json.dumps(data, default=str)
90 91  
  92 + @cherrypy.tools.accept(media='application/json') # FIXME
91 93 def POST(self, **args):
92 94 # print('POST', args) # FIXME
93 95 if args['cmd'] == 'allow':
... ... @@ -106,6 +108,25 @@ class AdminWebService(object):
106 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 130 # Webserver root
110 131 # ============================================================================
111 132 class Root(object):
... ... @@ -287,6 +308,7 @@ if __name__ == '__main__':
287 308 # --- create webserver
288 309 webapp = Root(app)
289 310 webapp.adminwebservice = AdminWebService(app)
  311 + webapp.studentwebservice = StudentWebService(app)
290 312  
291 313 # --- site wide configuration (valid for all apps)
292 314 cherrypy.tools.secureheaders = cherrypy.Tool('before_finalize', secureheaders, priority=60)
... ... @@ -312,6 +334,11 @@ if __name__ == '__main__':
312 334 'tools.response_headers.on': True,
313 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 342 '/static': {
316 343 'tools.auth.on': False, # everything in /static is public
317 344 'tools.staticdir.on': True,
... ...
static/css/test.css 0 → 100644
... ... @@ -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 57 var active = [];
58 58 $.each(students, function(i, r) {
59 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 63 // sort by start time
64 64 active.sort(function(a,b){return a[2] < b[2] ? -1 : (a[2] == b[2] ? 0 : 1);});
65 65 n = active.length;
66 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 73 </tr>';
74 74 }
75 75 $("#online_students").html(rows);
76 76 $("#online-header").html(n + " Activo(s)");
  77 +
77 78 }
78 79  
79 80 // ----------------------------------------------------------------------
... ... @@ -111,14 +112,12 @@ $(document).ready(function() {
111 112 rows += '<tr id="' + uid + '" + class="">';
112 113  
113 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 117 // (d['online'] ? '<span class="label label-warning">online</span>' : '') +
120   - (d['start_time']==''?'':'<span class="label label-success">teste</span>') +
121 118 '</td>\
  119 + <td>' + uid + '</td>\
  120 + <td>' + d['name'] + (d['password_defined'] ? ' <span class="label label-default">pw</span>' : '') +'</td>\
122 121 <td>';
123 122 var g = d['grades'];
124 123 var glength = g.length;
... ...
static/js/detect_unfocus.js
... ... @@ -2,10 +2,11 @@ $(document).ready(function() {
2 2 $(window).focus(function(){
3 3 $.ajax({
4 4 type: "POST",
5   - url: "/adminwebservice",
  5 + url: "/studentwebservice",
6 6 data: {
7 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 14 $(window).blur(function(e){
14 15 $.ajax({
15 16 type: "POST",
16   - url: "/adminwebservice",
  17 + url: "/studentwebservice",
17 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 90 <tr>
91 91 <th>Número</th>
92 92 <th>Nome</th>
93   - <th>Data de início</th>
  93 + <!-- <th>Data de início</th> -->
94 94 <th>Hora de início</th>
95 95 <th>IP</th>
  96 + <th>Estado</th>
96 97 </tr>
97 98 </thead>
98 99 <tbody id="online_students">
... ... @@ -113,7 +114,7 @@
113 114 <th>Perm.</th>
114 115 <th>Número</th>
115 116 <th>Nome</th>
116   - <th>Estado</th>
  117 + <!-- <th>Estado</th> -->
117 118 <th>Nota</th>
118 119 </tr>
119 120 </thead>
... ...
templates/test.html
... ... @@ -10,18 +10,19 @@
10 10 <!-- MathJax -->
11 11 <script type="text/x-mathjax-config">
12 12 MathJax.Hub.Config({
13   - tex2jax: {inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]]}
  13 + tex2jax: {
  14 + inlineMath: [["$$$","$$$"], ["$","$"], ["\\(","\\)"]]
  15 + }
14 16 });
15 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 20 <!-- Bootstrap -->
21 21 <link rel="stylesheet" href="/static/css/bootstrap.min.css">
22 22 <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <!-- optional -->
23 23 <link rel="stylesheet" href="/static/css/github.css"> <!-- syntax highlight -->
24 24 <link rel="stylesheet" href="/static/css/sticky-footer-navbar.css">
  25 + <link rel="stylesheet" href="/static/css/test.css">
25 26  
26 27 <script src="/static/js/jquery.min.js"></script>
27 28 <script src="/static/js/bootstrap.min.js"></script>
... ... @@ -32,26 +33,6 @@
32 33 <script src="/static/js/tabkey_in_textarea.js"></script>
33 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 36 </head>
56 37 <!-- ===================================================================== -->
57 38 <body>
... ... @@ -59,7 +40,6 @@
59 40 <div class="container-fluid drop-shadow">
60 41 <div class="navbar-header">
61 42 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#myNavbar">
62   - <!-- <span class="glyphicon glyphicon-menu-hamburger"></span> -->
63 43 <span class="icon-bar"></span>
64 44 <span class="icon-bar"></span>
65 45 <span class="icon-bar"></span>
... ... @@ -85,7 +65,6 @@
85 65 <li><a href="/results">Ver resultados</a></li>
86 66 % endif
87 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 68 </ul>
90 69 </li>
91 70 </ul>
... ...
tools.py
... ... @@ -22,7 +22,6 @@ def load_yaml(filename, default=None):
22 22 try:
23 23 return yaml.load(f)
24 24 except yaml.YAMLError as e:
25   - # except yaml.parser.ParserError:
26 25 mark = e.problem_mark
27 26 logger.error('In YAML file "{0}" near line {1}, column {2}.'.format(filename, mark.line, mark.column+1))
28 27 return default
... ...