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 | 26 | (r'/login', LoginHandler), |
27 | 27 | (r'/logout', LogoutHandler), |
28 | 28 | (r'/test', TestHandler), |
29 | - (r'/review', ReviewHandler), # FIXME | |
30 | - # (r'/admin', AdminHandler), # FIXME | |
29 | + (r'/review', ReviewHandler), # FIXME | |
30 | + (r'/admin', AdminHandler), | |
31 | 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 | 36 | settings = { |
... | ... | @@ -177,7 +177,7 @@ class ReviewHandler(BaseHandler): |
177 | 177 | try: |
178 | 178 | f = open(path.expanduser(fname)) |
179 | 179 | except FileNotFoundError: |
180 | - logging.error('Cannot find "{}" for review.'.format(fname)) | |
180 | + logging.error(f'Cannot find "{fname}" for review.') | |
181 | 181 | except Exception as e: |
182 | 182 | raise e |
183 | 183 | else: |
... | ... | @@ -211,72 +211,62 @@ class GiveupHandler(BaseHandler): |
211 | 211 | class RootHandler(BaseHandler): |
212 | 212 | @tornado.web.authenticated |
213 | 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 | 222 | class AdminHandler(BaseHandler): |
223 | + | |
224 | + | |
226 | 225 | @tornado.web.authenticated |
227 | 226 | def get(self): |
228 | 227 | if self.current_user != '0': |
229 | 228 | self.redirect('/') |
230 | 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 | 267 | # # Student webservice |
271 | 268 | # # ============================================================================ |
272 | 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 | 271 | # @cherrypy.tools.accept(media='application/json') # FIXME |
282 | 272 | # def POST(self, **args): |
... | ... | @@ -285,13 +275,6 @@ class AdminHandler(BaseHandler): |
285 | 275 | # v = json.loads(args['value']) |
286 | 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 | 16 | var number = $("#reset_number").val(); |
17 | 17 | $.ajax({ |
18 | 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 | 25 | function () { |
26 | 26 | $.ajax({ |
27 | 27 | type: "POST", |
28 | - url: "/adminwebservice", | |
28 | + url: "/admin", | |
29 | 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 | 41 | // ---------------------------------------------------------------------- |
40 | 42 | // checkbox handler to allow/deny students individually |
41 | 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 | 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 | 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 | 95 | function generate_grade_bar(grade) { |
82 | 96 | var barcolor; |
83 | 97 | if (grade < 10) { |
84 | - barcolor = 'progress-bar-danger'; | |
98 | + barcolor = 'bg-danger'; | |
85 | 99 | } |
86 | 100 | else if (grade < 15) { |
87 | - barcolor = 'progress-bar-warning'; | |
101 | + barcolor = 'bg-warning'; | |
88 | 102 | } |
89 | 103 | else { |
90 | - barcolor = 'progress-bar-success'; | |
104 | + barcolor = 'bg-success'; | |
91 | 105 | } |
92 | 106 | |
93 | 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 | 147 | // ---------------------------------------------------------------------- |
134 | 148 | function populate() { |
135 | 149 | $.ajax({ |
136 | - url: "/adminwebservice", | |
150 | + type: "POST", | |
151 | + url: "/admin", | |
152 | + data: {"cmd": "get_students", "value": ""}, | |
137 | 153 | dataType: "json", |
138 | 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 | 155 | // fill jumbotron data |
144 | 156 | $("#title").html(data['test']['title']); |
145 | 157 | $("#ref").html(data['test']['ref']); | ... | ... |
... | ... | @@ -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> | ... | ... |