Commit 6cebde6224e26b4087d20aca6c5212fa0736b676
1 parent
f87485b8
Exists in
master
and in
1 other branch
- added functional admin page.
Showing
3 changed files
with
243 additions
and
78 deletions
Show diff stats
serve.py
@@ -26,11 +26,11 @@ class WebApplication(tornado.web.Application): | @@ -26,11 +26,11 @@ class WebApplication(tornado.web.Application): | ||
26 | (r'/login', LoginHandler), | 26 | (r'/login', LoginHandler), |
27 | (r'/logout', LogoutHandler), | 27 | (r'/logout', LogoutHandler), |
28 | (r'/test', TestHandler), | 28 | (r'/test', TestHandler), |
29 | - (r'/review', ReviewHandler), # FIXME | ||
30 | - # (r'/admin', AdminHandler), # FIXME | 29 | + (r'/review', ReviewHandler), # FIXME |
30 | + (r'/admin', AdminHandler), | ||
31 | # (r'/change_password', ChangePasswordHandler), | 31 | # (r'/change_password', ChangePasswordHandler), |
32 | - (r'/static/(.+)', FileHandler), | ||
33 | - (r'/', RootHandler), | 32 | + (r'/static/(.+)', FileHandler), # FIXME |
33 | + (r'/', RootHandler), # TODO multiple tests | ||
34 | ] | 34 | ] |
35 | 35 | ||
36 | settings = { | 36 | settings = { |
@@ -177,7 +177,7 @@ class ReviewHandler(BaseHandler): | @@ -177,7 +177,7 @@ class ReviewHandler(BaseHandler): | ||
177 | try: | 177 | try: |
178 | f = open(path.expanduser(fname)) | 178 | f = open(path.expanduser(fname)) |
179 | except FileNotFoundError: | 179 | except FileNotFoundError: |
180 | - logging.error('Cannot find "{}" for review.'.format(fname)) | 180 | + logging.error(f'Cannot find "{fname}" for review.') |
181 | except Exception as e: | 181 | except Exception as e: |
182 | raise e | 182 | raise e |
183 | else: | 183 | else: |
@@ -211,72 +211,62 @@ class GiveupHandler(BaseHandler): | @@ -211,72 +211,62 @@ class GiveupHandler(BaseHandler): | ||
211 | class RootHandler(BaseHandler): | 211 | class RootHandler(BaseHandler): |
212 | @tornado.web.authenticated | 212 | @tornado.web.authenticated |
213 | def get(self): | 213 | def get(self): |
214 | - # if self.current_user == '0': | ||
215 | - # self.redirect('/admin') | ||
216 | - # else: | ||
217 | - # self.redirect('/test') | ||
218 | - # self.write("Hello, world") | ||
219 | - self.redirect('/test') | ||
220 | - | 214 | + if self.current_user == '0': |
215 | + self.redirect('/admin') | ||
216 | + else: | ||
217 | + self.redirect('/test') | ||
221 | 218 | ||
222 | # ============================================================================ | 219 | # ============================================================================ |
223 | -# Admin | 220 | +# Admin FIXME |
224 | # ============================================================================ | 221 | # ============================================================================ |
225 | class AdminHandler(BaseHandler): | 222 | class AdminHandler(BaseHandler): |
223 | + | ||
224 | + | ||
226 | @tornado.web.authenticated | 225 | @tornado.web.authenticated |
227 | def get(self): | 226 | def get(self): |
228 | if self.current_user != '0': | 227 | if self.current_user != '0': |
229 | self.redirect('/') | 228 | self.redirect('/') |
230 | self.render('admin.html') | 229 | self.render('admin.html') |
231 | 230 | ||
232 | -# # ============================================================================ | ||
233 | -# # Admin webservice | ||
234 | -# # ============================================================================ | ||
235 | -# class AdminWebService(object): | ||
236 | -# exposed = True | ||
237 | -# _cp_config = { | ||
238 | -# 'auth.require': [name_is('0')] | ||
239 | -# } | ||
240 | 231 | ||
241 | -# def __init__(self, app): | ||
242 | -# self.app = app | 232 | + @tornado.web.authenticated |
233 | + def post(self): | ||
234 | + print('admin post') | ||
235 | + if self.current_user != '0': | ||
236 | + self.redirect('/') | ||
243 | 237 | ||
244 | -# @cherrypy.tools.accept(media='application/json') # FIXME | ||
245 | -# def GET(self): | ||
246 | -# data = { | ||
247 | -# 'students': self.app.get_students_state(), | ||
248 | -# 'test': self.app.testfactory | ||
249 | -# } | ||
250 | -# return json.dumps(data, default=str) | 238 | + cmd = self.get_body_argument('cmd', None) |
239 | + value = self.get_body_argument('value', None) | ||
251 | 240 | ||
252 | -# @cherrypy.tools.accept(media='application/json') # FIXME | ||
253 | -# def POST(self, **args): | ||
254 | -# if args['cmd'] == 'allow': | ||
255 | -# if args['value'] == 'true': | ||
256 | -# return self.app.allow_student(args['name']) | ||
257 | -# else: | ||
258 | -# return self.app.deny_student(args['name']) | 241 | + if cmd == 'get_students': |
242 | + data = { | ||
243 | + 'students': self.testapp.get_students_state(), | ||
244 | + 'test': self.testapp.testfactory | ||
245 | + } | ||
246 | + self.write(json.dumps(data, default=str)) | ||
247 | + | ||
248 | + elif cmd == 'allow': | ||
249 | + self.write(self.testapp.allow_student(value)) | ||
259 | 250 | ||
260 | -# elif args['cmd'] == 'reset': | ||
261 | -# return self.app.reset_password(args['name']) | 251 | + elif cmd == 'deny': |
252 | + self.write(self.testapp.deny_student(value)) | ||
253 | + | ||
254 | + elif cmd == 'reset_password': | ||
255 | + self.write(self.testapp.reset_password(value)) | ||
256 | + | ||
257 | + elif cmd == 'insert_student': | ||
258 | + value = json.loads(value) | ||
259 | + self.write(self.testapp.insert_new_student(uid=value['number'], name=value['name'])) | ||
260 | + | ||
261 | + else: | ||
262 | + logging.error(f'Unknown command in post: "{cmd}"') | ||
262 | 263 | ||
263 | -# elif args['cmd'] == 'insert': | ||
264 | -# return self.app.insert_new_student(uid=args['number'], name=args['name']) | ||
265 | 264 | ||
266 | -# else: | ||
267 | -# print(args) # FIXME | ||
268 | 265 | ||
269 | # # ============================================================================ | 266 | # # ============================================================================ |
270 | # # Student webservice | 267 | # # Student webservice |
271 | # # ============================================================================ | 268 | # # ============================================================================ |
272 | # class StudentWebService(object): | 269 | # class StudentWebService(object): |
273 | -# exposed = True | ||
274 | -# _cp_config = { | ||
275 | -# 'auth.require': [] | ||
276 | -# } | ||
277 | - | ||
278 | -# def __init__(self, app): | ||
279 | -# self.app = app | ||
280 | 270 | ||
281 | # @cherrypy.tools.accept(media='application/json') # FIXME | 271 | # @cherrypy.tools.accept(media='application/json') # FIXME |
282 | # def POST(self, **args): | 272 | # def POST(self, **args): |
@@ -285,13 +275,6 @@ class AdminHandler(BaseHandler): | @@ -285,13 +275,6 @@ class AdminHandler(BaseHandler): | ||
285 | # v = json.loads(args['value']) | 275 | # v = json.loads(args['value']) |
286 | # self.app.set_student_focus(uid=args['number'], value=v) | 276 | # self.app.set_student_focus(uid=args['number'], value=v) |
287 | 277 | ||
288 | -# ============================================================================ | ||
289 | -# Webserver root | ||
290 | -# ============================================================================ | ||
291 | -# class Root(object): | ||
292 | -# def __init__(self, app): | ||
293 | -# self.app = app | ||
294 | - | ||
295 | 278 | ||
296 | 279 | ||
297 | # ------------------------------------------------------------------------- | 280 | # ------------------------------------------------------------------------- |
static/js/admin.js
@@ -16,8 +16,8 @@ $(document).ready(function() { | @@ -16,8 +16,8 @@ $(document).ready(function() { | ||
16 | var number = $("#reset_number").val(); | 16 | var number = $("#reset_number").val(); |
17 | $.ajax({ | 17 | $.ajax({ |
18 | type: "POST", | 18 | type: "POST", |
19 | - url: "/adminwebservice", | ||
20 | - data: {"cmd": "reset", "name": number} | 19 | + url: "/admin", |
20 | + data: {"cmd": "reset_password", "value": number} | ||
21 | }); | 21 | }); |
22 | } | 22 | } |
23 | ); | 23 | ); |
@@ -25,11 +25,13 @@ $(document).ready(function() { | @@ -25,11 +25,13 @@ $(document).ready(function() { | ||
25 | function () { | 25 | function () { |
26 | $.ajax({ | 26 | $.ajax({ |
27 | type: "POST", | 27 | type: "POST", |
28 | - url: "/adminwebservice", | 28 | + url: "/admin", |
29 | data: { | 29 | data: { |
30 | - "cmd": "insert", | ||
31 | - "number": $("#novo_numero").val(), | ||
32 | - "name": $("#novo_nome").val() | 30 | + "cmd": "insert_student", |
31 | + "value": JSON.stringify({ | ||
32 | + "number": $("#novo_numero").val(), | ||
33 | + "name": $("#novo_nome").val() | ||
34 | + }) | ||
33 | } | 35 | } |
34 | }); | 36 | }); |
35 | } | 37 | } |
@@ -39,15 +41,27 @@ $(document).ready(function() { | @@ -39,15 +41,27 @@ $(document).ready(function() { | ||
39 | // ---------------------------------------------------------------------- | 41 | // ---------------------------------------------------------------------- |
40 | // checkbox handler to allow/deny students individually | 42 | // checkbox handler to allow/deny students individually |
41 | function autorizeStudent(e) { | 43 | function autorizeStudent(e) { |
42 | - $.ajax({ | ||
43 | - type: "POST", | ||
44 | - url: "/adminwebservice", | ||
45 | - data: {"cmd": "allow", "name": this.name, "value": this.checked} | ||
46 | - }); | ||
47 | - if (this.checked) | 44 | + // $.ajax({ |
45 | + // type: "POST", | ||
46 | + // url: "/admin", | ||
47 | + // data: {"cmd": "allow", "name": this.name, "value": this.checked} | ||
48 | + // }); | ||
49 | + if (this.checked) { | ||
48 | $(this).parent().parent().addClass("active"); | 50 | $(this).parent().parent().addClass("active"); |
49 | - else | 51 | + $.ajax({ |
52 | + type: "POST", | ||
53 | + url: "/admin", | ||
54 | + data: {"cmd": "allow", "value": this.name} | ||
55 | + }); | ||
56 | + } | ||
57 | + else { | ||
50 | $(this).parent().parent().removeClass("active"); | 58 | $(this).parent().parent().removeClass("active"); |
59 | + $.ajax({ | ||
60 | + type: "POST", | ||
61 | + url: "/admin", | ||
62 | + data: {"cmd": "deny", "value": this.name} | ||
63 | + }); | ||
64 | + } | ||
51 | } | 65 | } |
52 | 66 | ||
53 | // ---------------------------------------------------------------------- | 67 | // ---------------------------------------------------------------------- |
@@ -81,13 +95,13 @@ $(document).ready(function() { | @@ -81,13 +95,13 @@ $(document).ready(function() { | ||
81 | function generate_grade_bar(grade) { | 95 | function generate_grade_bar(grade) { |
82 | var barcolor; | 96 | var barcolor; |
83 | if (grade < 10) { | 97 | if (grade < 10) { |
84 | - barcolor = 'progress-bar-danger'; | 98 | + barcolor = 'bg-danger'; |
85 | } | 99 | } |
86 | else if (grade < 15) { | 100 | else if (grade < 15) { |
87 | - barcolor = 'progress-bar-warning'; | 101 | + barcolor = 'bg-warning'; |
88 | } | 102 | } |
89 | else { | 103 | else { |
90 | - barcolor = 'progress-bar-success'; | 104 | + barcolor = 'bg-success'; |
91 | } | 105 | } |
92 | 106 | ||
93 | var bar = '<div class="progress"><div class="progress-bar ' + barcolor + '" role="progressbar" aria-valuenow="' + grade + '" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ' + (5*grade) + '%;">' + grade + '</div></div>'; | 107 | var bar = '<div class="progress"><div class="progress-bar ' + barcolor + '" role="progressbar" aria-valuenow="' + grade + '" aria-valuemin="0" aria-valuemax="20" style="min-width: 2em; width: ' + (5*grade) + '%;">' + grade + '</div></div>'; |
@@ -133,13 +147,11 @@ $(document).ready(function() { | @@ -133,13 +147,11 @@ $(document).ready(function() { | ||
133 | // ---------------------------------------------------------------------- | 147 | // ---------------------------------------------------------------------- |
134 | function populate() { | 148 | function populate() { |
135 | $.ajax({ | 149 | $.ajax({ |
136 | - url: "/adminwebservice", | 150 | + type: "POST", |
151 | + url: "/admin", | ||
152 | + data: {"cmd": "get_students", "value": ""}, | ||
137 | dataType: "json", | 153 | dataType: "json", |
138 | success: function(data) { | 154 | success: function(data) { |
139 | - // show clock on upper left corner | ||
140 | - var t = new Date(); | ||
141 | - $('#currenttime').html(t.getHours() + (t.getMinutes() < 10 ? ':0' : ':') + t.getMinutes()); | ||
142 | - | ||
143 | // fill jumbotron data | 155 | // fill jumbotron data |
144 | $("#title").html(data['test']['title']); | 156 | $("#title").html(data['test']['title']); |
145 | $("#ref").html(data['test']['ref']); | 157 | $("#ref").html(data['test']['ref']); |
@@ -0,0 +1,170 @@ | @@ -0,0 +1,170 @@ | ||
1 | +<!DOCTYPE html> | ||
2 | +<html lang="pt-PT"> | ||
3 | +<head> | ||
4 | + <title>Admin</title> | ||
5 | + <meta charset="utf-8"> | ||
6 | + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
7 | + <link rel="icon" href="/static/favicon.ico"> | ||
8 | + | ||
9 | + <!-- Bootstrap --> | ||
10 | + <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css"> | ||
11 | + | ||
12 | + <!-- optional --> | ||
13 | + <link rel="stylesheet" href="/static/font-awesome/css/font-awesome.min.css"> | ||
14 | + <link rel="stylesheet" href="/static/css/test.css"> | ||
15 | + | ||
16 | + <style> | ||
17 | + body { | ||
18 | + padding-top: 100px; | ||
19 | + background: #ccc; | ||
20 | + } | ||
21 | + </style> | ||
22 | +</head> | ||
23 | +<!-- ===================================================================== --> | ||
24 | +<body> | ||
25 | +<nav class="navbar navbar-expand-lg fixed-top navbar-dark bg-dark"> | ||
26 | + <a class="navbar-brand" href="#"> | ||
27 | + <i class="fa fa-clock-o" aria-hidden="true"></i> | ||
28 | + <span id="clock"> --:-- </span> | ||
29 | + </a> | ||
30 | + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation"> | ||
31 | + <span class="navbar-toggler-icon"></span> | ||
32 | + </button> | ||
33 | + | ||
34 | + <div class="collapse navbar-collapse" id="navbarText"> | ||
35 | + <ul class="navbar-nav mr-auto"> | ||
36 | + </ul> | ||
37 | + | ||
38 | + <span class="navbar-text"> | ||
39 | + <i class="fa fa-user" aria-hidden="true"></i> | ||
40 | + <span id="name">Admin</span> | ||
41 | + <span class="caret"></span> | ||
42 | + </span> | ||
43 | + </div> | ||
44 | +</nav> | ||
45 | +<!-- ===================================================================== --> | ||
46 | +<div class="container-fluid"> | ||
47 | +<!-- ===================================================================== --> | ||
48 | + <div class="jumbotron"> | ||
49 | + <h3 id="title"></h3> | ||
50 | + Ref: <span id="ref"></span><br> | ||
51 | + Enunciado: <span id="filename"></span><br> | ||
52 | + Database: <span id="database"></span><br> | ||
53 | + Testes entregues: <span id="answers_dir"></span> | ||
54 | + </div> <!-- jumbotron --> | ||
55 | + | ||
56 | +<!-- ===================================================================== --> | ||
57 | + <div class="card border-dark"> | ||
58 | + <div class="card-header" id="online-header"> | ||
59 | + <!-- to be populated --> | ||
60 | + </div> | ||
61 | + <div class="card-body"> | ||
62 | + <table class="table table-condensed noleftmargin"> | ||
63 | + <thead> | ||
64 | + <tr> | ||
65 | + <th>Número</th> | ||
66 | + <th>Nome</th> | ||
67 | + <!-- <th>Data de início</th> --> | ||
68 | + <th>Início</th> | ||
69 | + <!-- <th>IP</th> --> | ||
70 | + <th>Estado</th> | ||
71 | + </tr> | ||
72 | + </thead> | ||
73 | + <tbody id="online_students"> | ||
74 | + <!-- to be populated --> | ||
75 | + </tbody> | ||
76 | + </table> | ||
77 | + </div> <!-- card-body --> | ||
78 | + </div> <!-- card --> | ||
79 | + | ||
80 | +<!-- ===================================================================== --> | ||
81 | + <div class="card border-dark"> | ||
82 | + <div class="card-header" id="students-header"> | ||
83 | + <!-- to be populated --> | ||
84 | + </div> | ||
85 | + | ||
86 | + <div class="card-body"> | ||
87 | + <table class="table noleftmargin"> | ||
88 | + <thead> | ||
89 | + <tr> | ||
90 | + <th>Perm.</th> | ||
91 | + <th>Número</th> | ||
92 | + <th>Nome</th> | ||
93 | + <!-- <th>Estado</th> --> | ||
94 | + <th>Nota</th> | ||
95 | + </tr> | ||
96 | + </thead> | ||
97 | + <tbody id="students"> | ||
98 | + <!-- to be populated --> | ||
99 | + </tbody> | ||
100 | + </table> | ||
101 | + </div> <!-- card-body --> | ||
102 | + | ||
103 | + <div class="card-footer"> | ||
104 | + <div class="row"> | ||
105 | + <div class="col-sm-4"> | ||
106 | + Permitir | ||
107 | + <button id="allow_all" class="btn btn-xs btn-primary">Todos</button> | ||
108 | + <button id="deny_all" class="btn btn-xs btn-primary">Nenhum</button> | ||
109 | + </div> | ||
110 | + <div class="col-sm-4"> | ||
111 | + <button id="novo_aluno" class="btn btn-xs btn-primary" data-toggle="modal" data-target="#novo_aluno_modal">Inserir novo aluno</button> | ||
112 | + </div> | ||
113 | + <div class="col-sm-4"> | ||
114 | + <div class="input-group input-group-sm"> | ||
115 | + <input id="reset_number" type="text" class="form-control" placeholder="Número"> | ||
116 | + <span class="input-group-btn"> | ||
117 | + <button id="reset_password" class="btn btn-primary" type="button">Reset password!</button> | ||
118 | + </span> | ||
119 | + </div> | ||
120 | + </div> | ||
121 | + </div> <!-- row --> | ||
122 | + </div> <!-- card-footer --> | ||
123 | + </div> <!-- card --> | ||
124 | +<!-- ===================================================================== --> | ||
125 | +</div> <!-- container --> | ||
126 | +<!-- ===================================================================== --> | ||
127 | + | ||
128 | + | ||
129 | + | ||
130 | + <div class="modal" id="novo_aluno_modal"> | ||
131 | + <div class="modal-dialog" role="document"> | ||
132 | + <div class="modal-content"> | ||
133 | + | ||
134 | + <div class="modal-header"> | ||
135 | + <h5 class="modal-title">Inserir novo aluno</h5> | ||
136 | + <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> | ||
137 | + </div> | ||
138 | + | ||
139 | + <div class="modal-body"> | ||
140 | + <p> | ||
141 | + Número: <input id="novo_numero" type="text"> | ||
142 | + </p> | ||
143 | + <p> | ||
144 | + Nome: <input id="novo_nome" type="text"> | ||
145 | + </p> | ||
146 | + </div> | ||
147 | + | ||
148 | + <div class="modal-footer"> | ||
149 | + <button type="button" class="btn btn-danger" data-dismiss="modal">Cancelar</button> | ||
150 | + <button id="inserir_novo_aluno" class="btn btn-danger" role="button" data-dismiss="modal">Inserir</button> | ||
151 | + </div> | ||
152 | + | ||
153 | + </div> | ||
154 | + </div> | ||
155 | + </div> | ||
156 | + | ||
157 | +<!-- ===================================================================== --> | ||
158 | + | ||
159 | + | ||
160 | + | ||
161 | +<!-- Scripts --> | ||
162 | + <script src="/static/js/jquery.min.js"></script> | ||
163 | + <script src="/static/popper/umd/popper.min.js"></script> | ||
164 | + <script src="/static/bootstrap/js/bootstrap.min.js"></script> | ||
165 | + | ||
166 | +<!-- My scripts --> | ||
167 | + <script src="/static/js/admin.js"></script> | ||
168 | + <script src="/static/js/clock.js"></script> | ||
169 | +</body> | ||
170 | +</html> |