Commit 6cebde6224e26b4087d20aca6c5212fa0736b676

Authored by Miguel Barao
1 parent f87485b8
Exists in master and in 1 other branch dev

- added functional admin page.

@@ -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']);
templates/admin.html 0 → 100644
@@ -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">&times;</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>