Finish Material redesign, everything should be working now

parent 997426b1
......@@ -51,7 +51,12 @@ module("browser_handler", function (require, exports) {
// Show a DOM hidden element
function show(elem) {
debug.log("Showing", elem.id);
elem.setAttribute("style", "display: block");
$(elem).fadeIn();
}
function hide(elem) {
debug.log("Hiding", elem.id);
$(elem).fadeOut();
}
// Return a new string capitalized
......@@ -117,52 +122,32 @@ module("browser_handler", function (require, exports) {
}
};
function swith(elem, callback) {
if (elem !== undefined) {
debug.log(elem, "selected");
if (callback !== undefined) {
callback();
}
} else {
debug.error(elem, "not selectable");
}
}
/* END HELPERS */
/* DOM Selecting initialization */
main(function () {
sede = doc.getElementById("sede");
swith(sede, function () {
sedeValue = query(".value", sede)[0];
sedeTimestamp = query(".timestamp", sede)[0];
sedeModifiedBy = query(".modified_by", sede)[0];
});
temp = doc.getElementById("temp");
swith(temp, function () {
tempValue = query(".value", temp)[0];
tempTrend = query(".trend", temp)[0];
});
msg = query("#last.msg")[0];
swith(msg, function () {
msgUser = query(".user", msg)[0];
msgTimestamp = query(".timestamp", msg)[0];
msgValue = query(".value", msg)[0];
});
sede = $("#sede")[0];
sedeValue = $("#sede .value")[0];
sedeTimestamp = $("#sede .timestamp")[0];
sedeModifiedBy = $("#sede .modified_by")[0];
title = doc.title;
swith(title);
temp = $("#temp")[0];
tempValue = $("#temp .value")[0];
tempTrend = $("#temp .trend")[0];
msg = $("#last.msg")[0];
msgUser = $("#last.msg .user")[0];
msgTimestamp = $("#last.msg .timestamp")[0];
msgValue = $("#last.msg .value")[0];
title = doc.title;
favicon = query('[rel="icon"]')[0];
swith(favicon);
head = doc.head || doc.getElementsByTagName('head')[0];
swith(head);
tempGraph = Raphael("temperature_graph", tempGraphWidth, tempGraphHeight);
if ($("#temperature_graph").length > 0)
tempGraph = Raphael("temperature_graph", tempGraphWidth, tempGraphHeight);
trend = new Trend();
});
......@@ -175,7 +160,7 @@ module("browser_handler", function (require, exports) {
if (first) {
show(sede);
}
var value = status.value === "open" ? "open" : "close";
var value = status.value === "open" ? "open" : "closed";
changeIcon(value);
changeFAB(value);
doc.title = capitalize(value) + " " + title;
......@@ -187,8 +172,10 @@ module("browser_handler", function (require, exports) {
// Handle MSGs arrival
function msgHandler(message, first) {
debug.log("Displaying message");
if (first) {
if (message.value.length > 0) {
show(msg);
} else if (message.value.length === 0) {
hide(msg);
}
msgUser.innerHTML = message.user;
msgTimestamp.innerHTML = message.timestamp;
......@@ -202,6 +189,7 @@ module("browser_handler", function (require, exports) {
if (first) {
show(temp);
}
tempValue.innerHTML = tempInt.value.toPrecision() + "°C";
if (lastTimestamp !== tempInt.timestamp) {
......@@ -210,7 +198,11 @@ module("browser_handler", function (require, exports) {
//TODO tempGraph.addTemp(tempInt);
}
}
temp.setAttribute("class", tempInt.value > 20 ? "high" : "low");
$(temp)
.removeClass("high")
.removeClass("low")
.addClass(tempInt.value > 20 ? "high" : "low");
}
......
......@@ -7,8 +7,9 @@ main(function (require) {
location = require("location"),
query = require("peppy").query,
debug = require("debug"),
protocol = /https/.test(location.protocol)? "wss": "ws",
ws = new WebSocket(protocol + "://" + location.hostname + ":" + location.port + "/ws"),
protocol = /https/.test(location.protocol) ? "wss" : "ws",
// ws = new WebSocket(protocol + "://" + location.hostname + ":" + location.port + "/ws"),
ws = new WebSocket("wss://bits.poul.org/ws"),
handler = new Handler(browserHandler);
var debugMeta = query("meta[name='mode']")[0];
......@@ -49,7 +50,6 @@ $(document).ready(function () {
const windowScroll = $(this).scrollTop();
const barBottom = navbar.outerHeight();
console.log("sticky? " + (windowScroll + barBottom > mainTop) + " offset " + (windowScroll + barBottom) + " " + mainTop);
setFABsticky(windowScroll + barBottom > mainTop);
});
});
\ No newline at end of file
......@@ -3,7 +3,19 @@
}
.bg-very-dark {
background-color: #212121 !important;
background-color: #010101 !important;
}
.bg-a-bit-less-dark {
background-color: #1b1b1b !important;
}
.bg-kinda-dark {
background-color: #232323 !important;
}
.bg-black {
background-color: black !important;
}
.page-header {
......@@ -15,6 +27,15 @@ a {
color: #ffc500;
}
a:hover {
color: #ac8300;
}
strong {
font-weight: bold;
color: #ffc500;
}
footer ul li a {
color: #ffc500;
}
......@@ -28,8 +49,6 @@ footer ul li a {
}
#hq-status-fab button {
position: -webkit-sticky; /* Safari */
position: sticky;
top: 0;
font-weight: bold;
}
......@@ -113,45 +132,121 @@ footer ul li a {
}
.btn.btn-close {
.btn.btn-closed {
color: #fff;
background-color: #d32f2f;
border-color: #d32f2f;
box-shadow: 0 2px 2px 0 rgba(105, 23, 23, 0.14), 0 3px 1px -2px rgba(105, 23, 23, 0.2), 0 1px 5px 0 rgba(105, 23, 23, 0.12);
}
.btn.btn-close :hover {
.btn.btn-closed:hover {
color: #fff;
background-color: #d32f2f;
border-color: #691717;
}
.btn.btn-closed:after {
content: 'Sede chiusa';
}
.btn-close {
.btn-closed {
color: #212529;
background-color: #d32f2f;
border-color: #d32f2f;
box-shadow: none;
}
.btn.btn-close :after {
content: 'Sede chiusa';
}
.btn-close :focus,
.btn-close .focus {
.btn-closed:focus,
.btn-closed.focus {
box-shadow: none, 0 0 0 0.2rem rgb(105, 23, 23);
}
.btn-close .disabled,
.btn-close :disabled {
.btn-closed.disabled,
.btn-closed:disabled {
color: #212529;
background-color: #d32f2f;
border-color: #d32f2f;
border-color: #691717;
}
.stick-to-bar {
position: fixed !important;
left: 0 !important;
top: 38px !important;
}
.closed.value:after {
content: 'Chiusa';
}
.open.value:after {
content: 'Aperta';
}
.msg .msg-inner {
font-style: italic;
}
#temp .card-body p {
font-size: 15pt;
}
#presence_graph {
width: 100%;
}
.card {
margin-top: 7px;
margin-bottom: 7px;
}
.card p {
margin-bottom: 0;
}
#sede, #temp, #last.msg {
display: none;
}
.md-content p {
font-size: 1.125rem;
line-height: 1.5em;
}
ul#log-status {
list-style: none;
}
#log-status .closed .fa-auto:before {
content: "\f023";
}
#log-status .open .fa-auto:before {
content: "\f09c";
}
#log-status .closed .fa-auto:after {
font-family: 'Open Sans', sans-serif;
content: " Chiusa, ";
}
#log-status .open .fa-auto:after {
font-family: 'Open Sans', sans-serif;
content: " Aperta, ";
}
#paginator {
text-align: center;
}
.btn.btn-transparent {
background-color: transparent;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
#admin .card {
margin-top: 50px;
margin-bottom: 50px;
}
\ No newline at end of file
......@@ -10,30 +10,25 @@
"""
HTTP requests handlers.
"""
import json
from datetime import datetime, timedelta
import json
import markdown
from datetime import datetime, timedelta
import tornado.auth
import tornado.websocket
from sqlalchemy import distinct
from sqlalchemy.exc import IntegrityError
from tornado.web import MissingArgumentError, HTTPError, RequestHandler
import tornado.websocket
import tornado.auth
from tornado.options import options
from tornado.web import MissingArgumentError, HTTPError, RequestHandler
import bitsd.listener.notifier as notifier
import bitsd.persistence.query as query
from bitsd.common import LOG, secure_compare
from bitsd.persistence.engine import session_scope, persist
from bitsd.persistence.models import Status, User, MACToUser, LoginAttempt
from .auth import verify, DoSError
from .presence import PresenceForecaster
from .notifier import MessageNotifier
import bitsd.persistence.query as query
from bitsd.common import LOG, secure_compare
from .presence import PresenceForecaster
def cache(seconds):
......@@ -51,13 +46,16 @@ def cache(seconds):
Parameters:
`seconds`: TTL of the cached resource, in seconds.
"""
def set_cacheable(get_function):
def wrapper(self, *args, **kwargs):
self.set_header("Expires", datetime.utcnow() +
timedelta(seconds=seconds))
timedelta(seconds=seconds))
self.set_header("Cache-Control", "max-age=" + str(seconds))
return get_function(self, *args, **kwargs)
return wrapper
return set_cacheable
......@@ -85,13 +83,15 @@ class BaseHandler(RequestHandler):
class HomePageHandler(BaseHandler):
"""Display homepage."""
@cache(86400*10)
@cache(86400 * 10)
def get(self):
self.render('templates/homepage.html')
class DataPageHandler(BaseHandler):
"""Get BITS data in JSON, machine parseable."""
def get(self):
with session_scope() as session:
self.write(query.get_latest_data(session))
......@@ -124,12 +124,12 @@ class LogPageHandler(BaseHandler):
self.finish()
else:
self.render('templates/log.html',
latest_statuses=latest_statuses,
# Used by the paginator
offset=offset,
limit=self.LINES_PER_PAGE,
count=query.get_number_of_statuses(session),
)
latest_statuses=latest_statuses,
# Used by the paginator
offset=offset,
limit=self.LINES_PER_PAGE,
count=query.get_number_of_statuses(session),
)
@staticmethod
def jsonize(latest_statuses):
......@@ -148,6 +148,7 @@ class LogPageHandler(BaseHandler):
class StatusPageHandler(BaseHandler):
"""Get a single digit, indicating BITS status (open/closed)"""
def get(self):
with session_scope() as session:
status = query.get_current_status(session)
......@@ -158,7 +159,8 @@ class StatusPageHandler(BaseHandler):
class MarkdownPageHandler(BaseHandler):
"""Renders page from markdown source."""
@cache(86400*10)
@cache(86400 * 10)
def get(self, slug):
with session_scope() as session:
page = query.get_page(session, slug)
......@@ -167,12 +169,12 @@ class MarkdownPageHandler(BaseHandler):
raise tornado.web.HTTPError(404)
self.render('templates/mdpage.html',
body=markdown.markdown(
page.body,
safe_mode='escape' if options.mdescape else False,
),
title=page.title,
)
body=markdown.markdown(
page.body,
safe_mode='escape' if options.mdescape else False,
),
title=page.title,
)
class StatusHandler(tornado.websocket.WebSocketHandler):
......@@ -192,7 +194,7 @@ class StatusHandler(tornado.websocket.WebSocketHandler):
"""Unregister this handler when the connection is closed."""
StatusHandler.CLIENTS.unregister(self)
LOG.debug('Unregistered client.')
def check_origin(self, origin):
"""No Same-Origin Policy"""
return True
......@@ -200,6 +202,7 @@ class StatusHandler(tornado.websocket.WebSocketHandler):
class LoginPageHandler(BaseHandler):
"""Handle login browser requests for reserved area."""
def get(self):
next = self.get_argument("next", "/")
if self.get_current_user():
......@@ -223,7 +226,8 @@ class LoginPageHandler(BaseHandler):
with session_scope() as session:
try:
verified = verify(session, username, password, ip_address, has_recaptcha, captcha_challenge, captcha_response)
verified = verify(session, username, password, ip_address, has_recaptcha, captcha_challenge,
captcha_response)
except DoSError as error:
LOG.warning("DoS protection: %s", error)
self.log_offender_details()
......@@ -277,9 +281,22 @@ class AdminPageHandler(BaseHandler):
@tornado.web.authenticated
def get(self):
"""Display the admin page."""
try:
with session_scope() as session:
curstatus = query.get_current_status(session)
if curstatus is None:
status = Status.CLOSED
else:
status = Status.OPEN if curstatus.value == Status.CLOSED else Status.CLOSED
except Exception:
status = Status.CLOSED
self.render(
'templates/admin.html',
page_message='Very secret information here',
status=status,
roster=MACUpdateHandler.ROSTER
)
......@@ -287,7 +304,8 @@ class AdminPageHandler(BaseHandler):
def post(self):
"""Issue admin commands."""
status = self.get_argument('changestatus', default=None)
if status: self.change_status()
if status:
self.change_status()
def change_status(self):
"""Manually change the status of the BITS system"""
......@@ -306,7 +324,7 @@ class AdminPageHandler(BaseHandler):
status = query.log_status(session, textstatus, 'web')
broadcast(status.jsondict())
notifier.send_status(textstatus)
message = "Ora la sede è {}.".format(textstatus)
message = "Ora la sede è {}.".format(textstatus == Status.OPEN and "aperta" or "chiusa")
except IntegrityError:
LOG.error("Status changed too quickly, not logged.")
message = "Errore: modifica troppo veloce!"
......@@ -315,6 +333,7 @@ class AdminPageHandler(BaseHandler):
self.render(
'templates/admin.html',
page_message=message,
status=textstatus,
roster=MACUpdateHandler.ROSTER
)
......@@ -402,10 +421,10 @@ class MACUpdateHandler(BaseHandler):
macs = json.loads(macs)
with session_scope() as session:
names = session.\
query(distinct(User.name)).\
filter(User.userid == MACToUser.userid).\
filter(MACToUser.mac_hash .in_ (macs)).\
names = session. \
query(distinct(User.name)). \
filter(User.userid == MACToUser.userid). \
filter(MACToUser.mac_hash.in_(macs)). \
all()
MACUpdateHandler.ROSTER = [n[0] for n in names]
......
......@@ -13,29 +13,62 @@
{% block subtitle %}Amministrazione{% end %}
{% block body %}
<div>{{ page_message }}</div>
<form action="/admin" method="post">
<p>
<input type="hidden" name="changestatus" value="change">
{% module xsrf_form_html() %}
<input type="submit" value="Cambia stato sede">
</p>
</form>
<div id="roster">
<h2>Utenti in sede</h2>
{% if roster %}
<ul>
{% for name in roster %}
<li>{{name}}</li>
{% end %}
</ul>
{% else %}
<p>Nessuno...</p>
{% end %}
<div id="hq-status-fab">
<form action="/admin" method="post">
<p>
<input type="hidden" name="changestatus" value="change">
{% module xsrf_form_html() %}
<input class="btn btn-round btn-lg btn-gialla btn-{{ status }}" type="submit" value="Cambia stato sede">
</p>
</form>
</div>
<div id="admin" class="row">
<div class="col col-12 col-lg-6 text-center">
<div class="card bg-kinda-dark">
<div class="card-header card-header-warning">
Messaggio
</div>
<div class="card-body">{{ page_message }}</div>
</div>
<div class="card bg-kinda-dark">
<div class="card-header card-header-warning">
Utenti in sede
</div>
<div id="roster" class="card-body">
{% if roster %}
<ul>
{% for name in roster %}
<li>{{ name }}</li>
{% end %}
</ul>
{% else %}
<p><i>Nessuno.</i></p>
{% end %}
</div>
</div>
</div>
<div class="col col-12 col-lg-6 text-center">
<div class="card bg-kinda-dark">
<div class="card-header card-header-warning">
Azioni
</div>
<div class="card-body">
<a id="logout" href="/logout">
<button class="btn btn-light">
<i class="material-icons">undo</i> Logout
</button>
</a>
<a id="messages" href="/message">
<button class="btn btn-light">
<i class="material-icons">message</i> Invia messaggio
</button>
</a>
</div>
</div>
</div>
</div>
<ul class="link">
<li><a id="logout" href="/logout">Logout</a></li>
<li><a id="messages" href="/message">Invia messaggio</a></li>
</ul>
{% end %}
......@@ -51,7 +51,8 @@
<div class="collapse navbar-collapse">
<ul class="navbar-nav ml-auto">
<li class="nav-item"><a class="nav-link" href="http://www.poul.org">Cos'è il POuL</a></li>
<li class="nav-item"><a class="nav-link" href="https://f-droid.org/packages/org.poul.bits.android/" target="_blank" rel="noopener"><i class="fa fa-f-droid"></i> App Android</a></li>
<li class="nav-item"><a class="nav-link" href="https://www.poul.org">Cos'è il POuL</a></li>
<li class="nav-item"><a class="nav-link" href="/info">Cos'è BITS</a></li>
<li class="nav-item"><a class="nav-link" href="/log">Log accessi</a></li>
</ul>
......@@ -60,8 +61,7 @@
</nav>
<div class="page-header header-filter" data-parallax="true"
style="background-image: url('/static/vendor/material-kit/img/bg3.jpg')">
<div class="page-header header-filter" data-parallax="true">
<div class="container">
<div class="row">
<div class="col-md-8 ml-auto mr-auto">
......@@ -222,9 +222,9 @@