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> | ... | ... |