mostly working templating for landing page
This commit is contained in:
@@ -4,6 +4,8 @@ import logging
|
|||||||
from typing import Generator
|
from typing import Generator
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
from templating import SensorInfo
|
||||||
|
|
||||||
# main database connector class
|
# main database connector class
|
||||||
# permission validation is not performed on this class
|
# permission validation is not performed on this class
|
||||||
# perform those higher on the call stack
|
# perform those higher on the call stack
|
||||||
@@ -36,10 +38,14 @@ class DatabaseConnect:
|
|||||||
def display_users(self) -> tuple[tuple[int, str, int]]:
|
def display_users(self) -> tuple[tuple[int, str, int]]:
|
||||||
self.cursor.execute("SELECT * FROM Users")
|
self.cursor.execute("SELECT * FROM Users")
|
||||||
return self.cursor.fetchall()
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
|
def username_from_id(self, user_id):
|
||||||
|
self.cursor.execute("SELECT users.`username` FROM users WHERE `ID` = 1;")
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
def view_valid_rooms(self, user_id) -> list[tuple[str, str]]:
|
def view_valid_rooms(self, user_id) -> list[tuple[str, str, int]]:
|
||||||
self.cursor.execute(
|
self.cursor.execute(
|
||||||
"SELECT rooms.name, rooms.shortname from permissions INNER JOIN rooms ON permissions.`roomID` = rooms.`ID` WHERE permissions.`userID` = ? AND permissions.`view` = 1;",
|
"SELECT rooms.name, rooms.shortname, rooms.ID from permissions INNER JOIN rooms ON permissions.`roomID` = rooms.`ID` WHERE permissions.`userID` = ? AND permissions.`view` = 1;",
|
||||||
(user_id,))
|
(user_id,))
|
||||||
return self.cursor.fetchall()
|
return self.cursor.fetchall()
|
||||||
|
|
||||||
@@ -55,9 +61,22 @@ class DatabaseConnect:
|
|||||||
# self.cursor.execute("SELECT rooms.`ID` from rooms WHERE rooms.shortname = ?;",(shortname,))
|
# self.cursor.execute("SELECT rooms.`ID` from rooms WHERE rooms.shortname = ?;",(shortname,))
|
||||||
# return self.cursor.fetchone()
|
# return self.cursor.fetchone()
|
||||||
|
|
||||||
def get_sensors_in_room(self, shortname) -> list[int]:
|
def get_sensors_in_room_shortname(self, shortname) -> list[int]:
|
||||||
self.cursor.execute("SELECT devices.`ID` from devices LEFT JOIN rooms ON devices.`roomID` = rooms.ID WHERE rooms.shortname = ?;",(shortname,))
|
self.cursor.execute("SELECT Sensors.`ID` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.shortname = ?;",(shortname,))
|
||||||
return [x[0] for x in self.cursor.fetchall()]
|
return [x[0] for x in self.cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_sensors_in_room_id(self, roomid) -> list[tuple[int, int]]:
|
||||||
|
self.cursor.execute("SELECT Sensors.`ID`, Sensors.`Type` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.`ID` = ?;",(roomid,))
|
||||||
|
return [(x[0], x[1]) for x in self.cursor.fetchall()]
|
||||||
|
|
||||||
|
def get_sensorsinfo_by_sensorid(self, sensorid) -> SensorInfo | None:
|
||||||
|
self.cursor.execute("SELECT readings.`Timestamp`, readings.`reading` FROM readings WHERE readings.`sensorID` = ? ORDER BY readings.`Timestamp` DESC FETCH FIRST 1 ROWS ONLY;",(sensorid,))
|
||||||
|
fetch = self.cursor.fetchone()
|
||||||
|
if fetch is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return SensorInfo(0,sensorid, fetch[0], fetch[1])
|
||||||
|
|
||||||
|
|
||||||
def get_sensor_data(self, sensor_ID: int) -> Generator[tuple[datetime.datetime, float]]:
|
def get_sensor_data(self, sensor_ID: int) -> Generator[tuple[datetime.datetime, float]]:
|
||||||
self.cursor.execute("SELECT Timestamp, reading FROM Readings WHERE `sensorID` = ? ORDER BY `Timestamp` DESC;",(sensor_ID,))
|
self.cursor.execute("SELECT Timestamp, reading FROM Readings WHERE `sensorID` = ? ORDER BY `Timestamp` DESC;",(sensor_ID,))
|
||||||
@@ -65,7 +84,7 @@ class DatabaseConnect:
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_sensor_type(self, sensor_ID):
|
def get_sensor_type(self, sensor_ID):
|
||||||
self.cursor.execute("SELECT types.`type_desc` from types LEFT JOIN devices ON types.`ID` = devices.`type` WHERE devices.`ID` = ?;",(sensor_ID,))
|
self.cursor.execute("SELECT types.`type_desc` from types LEFT JOIN Sensors ON types.`ID` = Sensors.`type` WHERE Sensors.`ID` = ?;",(sensor_ID,))
|
||||||
return self.cursor.fetchone()[0]
|
return self.cursor.fetchone()[0]
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -9,13 +9,18 @@ INSERT INTO Users (`ID`, `username`, `pwd`) VALUES (1,'nouser',NULL), (NULL,'Adm
|
|||||||
|
|
||||||
CREATE TABLE Permissions (`userID` INT, `roomID` INT, `view` BOOLEAN DEFAULT 0, `purge_data` BOOLEAN DEFAULT 0, `administer` BOOLEAN DEFAULT 0);
|
CREATE TABLE Permissions (`userID` INT, `roomID` INT, `view` BOOLEAN DEFAULT 0, `purge_data` BOOLEAN DEFAULT 0, `administer` BOOLEAN DEFAULT 0);
|
||||||
CREATE TABLE Rooms (`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` TEXT, `shortname` TEXT UNIQUE);
|
CREATE TABLE Rooms (`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `name` TEXT, `shortname` TEXT UNIQUE);
|
||||||
CREATE TABLE Devices(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type` INT, `roomID` INT, `mqttTopic` TEXT);
|
CREATE TABLE Sensors(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type` INT, `roomID` INT, `mqttTopic` TEXT);
|
||||||
CREATE TABLE Types(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type_desc` TEXT);
|
CREATE TABLE Types(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `type_desc` TEXT);
|
||||||
|
|
||||||
|
CREATE TABLE Devices(`ID` INT NOT NULL PRIMARY KEY AUTO_INCREMENT, `roomID` INT, `name` TEXT, `description` TEXT);
|
||||||
|
|
||||||
CREATE TABLE Readings(`sensorID` INT, `Timestamp` DATETIME, `reading` DOUBLE);
|
CREATE TABLE Readings(`sensorID` INT, `Timestamp` DATETIME, `reading` DOUBLE);
|
||||||
CREATE INDEX `sensor_index` ON Readings (`sensorID`);
|
CREATE INDEX `sensor_index` ON Readings (`sensorID`);
|
||||||
|
|
||||||
|
CREATE TABLE Log( `timestamp` TIMESTAMP, `type` INT, `message` TEXT);
|
||||||
|
|
||||||
INSERT INTO Rooms (`name`,`shortname`) VALUES ('101','101'),('102','102'),('210','210'),('211','211'),('215 - Studovna','215');
|
INSERT INTO Rooms (`name`,`shortname`) VALUES ('101','101'),('102','102'),('210','210'),('211','211'),('215 - Studovna','215');
|
||||||
INSERT INTO Devices (`type`, `roomID`,`mqttTopic`) VALUES (1,1,"101/floortemp"),(1,1,"101/ceiltemp"),(2,1,"101/humidity"),(1,3,"210/temp"),(1,5,"215/temp"),(2,5,"215/humidity");
|
INSERT INTO Sensors (`type`, `roomID`,`mqttTopic`) VALUES (0,1,"101/floortemp"),(0,1,"101/ceiltemp"),(1,1,"101/humidity"),(0,3,"210/temp"),(0,5,"215/temp"),(1,5,"215/humidity");
|
||||||
INSERT INTO Types (type_desc) VALUES ("Temperature"), ('Humidity');
|
INSERT INTO Types (type_desc) VALUES ("Temperature"), ('Humidity');
|
||||||
INSERT INTO `readings`(`Timestamp`,`reading`,`sensorID`) VALUES
|
INSERT INTO `readings`(`Timestamp`,`reading`,`sensorID`) VALUES
|
||||||
('2025-11-12 00:00:00',25.7,1),('2025-11-12 01:00:00',26.7,1),('2025-11-12 02:00:00',27.4,1),('2025-11-12 02:04:00',28.0,1),('2025-11-12 03:22:00',28.2,1),
|
('2025-11-12 00:00:00',25.7,1),('2025-11-12 01:00:00',26.7,1),('2025-11-12 02:00:00',27.4,1),('2025-11-12 02:04:00',28.0,1),('2025-11-12 03:22:00',28.2,1),
|
||||||
@@ -31,7 +36,10 @@ SELECT rooms.name, rooms.shortname from permissions INNER JOIN rooms ON permissi
|
|||||||
|
|
||||||
SELECT rooms.`ID` from rooms WHERE rooms.shortname = '101';
|
SELECT rooms.`ID` from rooms WHERE rooms.shortname = '101';
|
||||||
|
|
||||||
SELECT devices.`ID` from devices LEFT JOIN rooms ON devices.`roomID` = rooms.ID WHERE rooms.shortname = '101';
|
SELECT Sensors.`ID` from Sensors LEFT JOIN rooms ON Sensors.`roomID` = rooms.ID WHERE rooms.shortname = '101';
|
||||||
SELECT types.`type_desc` from types LEFT JOIN devices ON types.`ID` = devices.`type` WHERE devices.`ID` = 1;
|
SELECT types.`type_desc` from types LEFT JOIN Sensors ON types.`ID` = Sensors.`type` WHERE Sensors.`ID` = 1;
|
||||||
|
|
||||||
SELECT permissions.`view` FROM permissions LEFT JOIN rooms ON permissions.`roomID` = rooms.ID WHERE rooms.shortname = '101' AND `userID` = 1;
|
SELECT permissions.`view` FROM permissions LEFT JOIN rooms ON permissions.`roomID` = rooms.ID WHERE rooms.shortname = '101' AND `userID` = 1;
|
||||||
|
|
||||||
|
# get latest reading from a sensor
|
||||||
|
SELECT readings.`Timestamp`, readings.`reading` FROM readings WHERE readings.`sensorID` = '1' ORDER BY readings.`Timestamp` DESC FETCH FIRST 1 ROWS ONLY;
|
||||||
|
|||||||
19
static/scripts.js
Normal file
19
static/scripts.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
function tableSearch(){
|
||||||
|
var table, input, tr, td, row_content;
|
||||||
|
table = document.getElementById("tableRooms");
|
||||||
|
input = document.getElementById("tableRoomSearch").value.toUpperCase();
|
||||||
|
tr = table.getElementsByTagName("tr");
|
||||||
|
|
||||||
|
for (let i = 0; i < tr.length; i++) {
|
||||||
|
td = tr[i].getElementsByTagName("td")[0];
|
||||||
|
if(td){
|
||||||
|
row_content = td.textContent || td.innerText;
|
||||||
|
if (row_content.toUpperCase().indexOf(input) > -1) {
|
||||||
|
tr[i].style.display = "";
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
tr[i].style.display ="none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ a{
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main{
|
.main-flex{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ a{
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content{
|
.right-flex{
|
||||||
flex: 1 60vw;
|
flex: 1 60vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ a{
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content{
|
.content{
|
||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +75,11 @@ thead{
|
|||||||
tbody tr{
|
tbody tr{
|
||||||
border-bottom: 0.1em darkgray solid;
|
border-bottom: 0.1em darkgray solid;
|
||||||
}
|
}
|
||||||
.missing-data{
|
.data-missing{
|
||||||
background-color: gray;
|
background-color: gray;
|
||||||
|
color: transparent
|
||||||
}
|
}
|
||||||
.late-data{
|
.data-late{
|
||||||
background-color: orange;
|
background-color: orange;
|
||||||
}
|
}
|
||||||
.room-table-elem, .searchbar{
|
.room-table-elem, .searchbar{
|
||||||
21
static_text.toml
Normal file
21
static_text.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Definitions for various pieces of static text
|
||||||
|
# Raw HTML may be inserted for further formatting purposes
|
||||||
|
|
||||||
|
pagetitle = "Room air quality Example Inc."
|
||||||
|
|
||||||
|
# used in the top left corner for identifying the website
|
||||||
|
# logos and links to the main pages are good ideas to insert here
|
||||||
|
top_branding = "Example Inc. <br>Room air quality monitoring"
|
||||||
|
|
||||||
|
|
||||||
|
# user greeting after they log in, this is the part that goes
|
||||||
|
# before the name itself
|
||||||
|
user_greeting_before = "Welcome back "
|
||||||
|
|
||||||
|
# followed by the part after the username
|
||||||
|
user_greeting_after= "!"
|
||||||
|
|
||||||
|
# landing information also displayed on the main page of a logged in user
|
||||||
|
# or a non logged in user if allowed
|
||||||
|
# this is typically a paragraph introducing the nature of the service to the user
|
||||||
|
landing_information = "This service can be used to monitor the air quality in most rooms of the Example Inc. headquarters so that you and your coworkers may know the room is suitable for use or may prepare the room before using it."
|
||||||
49
templating.py
Normal file
49
templating.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from flask import url_for, Flask
|
||||||
|
from jinja2 import FileSystemLoader
|
||||||
|
|
||||||
|
import tomllib
|
||||||
|
from os import sep
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
def static_test_load():
|
||||||
|
with open("static_text.toml", "rb") as f:
|
||||||
|
return tomllib.load(f)
|
||||||
|
|
||||||
|
UserInfo = namedtuple('UserInfo',['id', 'name'])
|
||||||
|
RoomInfo = namedtuple('RoomInfo',['name', 'shortcode', 'roomdata'])
|
||||||
|
# state is an int not in db
|
||||||
|
# 0 - value valid
|
||||||
|
# 1 - value late
|
||||||
|
# 2 - value missing
|
||||||
|
SensorInfo = namedtuple('SensorInfo',['state','type','timestamp','reading'])
|
||||||
|
|
||||||
|
# base class for inheriting other more specific pages
|
||||||
|
class BasePage():
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
jijna_env,
|
||||||
|
target_path = "base.html.jinja" ,
|
||||||
|
statictext = None,
|
||||||
|
) -> None:
|
||||||
|
self.env = jijna_env
|
||||||
|
self.target = target_path
|
||||||
|
self.rendervars = static_test_load() if statictext is None else statictext
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
url_for('static', filename="styles.css")
|
||||||
|
url_for('static', filename="scripts.css")
|
||||||
|
template = self.env.get_template(self.target)
|
||||||
|
return template.render(self.rendervars)
|
||||||
|
|
||||||
|
class LandingPage(BasePage):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
jinja_env,
|
||||||
|
userinfo,
|
||||||
|
roominfo,
|
||||||
|
target_path="landing.html.jinja",
|
||||||
|
statictext=None,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(jinja_env, target_path, statictext)
|
||||||
|
self.rendervars["userinfo"] = userinfo
|
||||||
|
self.rendervars["roominfo"] = roominfo
|
||||||
32
test.py
Normal file
32
test.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from jinja2 import Template
|
||||||
|
|
||||||
|
text = """
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="utf-8" /> <!--It is necessary to use the UTF-8 encoding with plotly graphics to get e.g. negative signs to render correctly -->
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{{ text1 }}
|
||||||
|
{{ text2 }}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
tmpl = Template(text)
|
||||||
|
print(
|
||||||
|
tmpl.render(
|
||||||
|
text1 = "TEST"
|
||||||
|
)
|
||||||
|
)
|
||||||
63
web.py
63
web.py
@@ -1,35 +1,71 @@
|
|||||||
from db_connect import DatabaseConnect
|
from db_connect import DatabaseConnect
|
||||||
|
|
||||||
from flask import Flask, abort, session
|
from flask import Flask, abort, session
|
||||||
from jinja2 import Template
|
from jinja2 import FileSystemLoader, Template
|
||||||
import plotly.express as px
|
import plotly.express as px
|
||||||
from plotly.subplots import make_subplots
|
from plotly.subplots import make_subplots
|
||||||
import plotly.graph_objects as go
|
import plotly.graph_objects as go
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from os import sep
|
from os import sep
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import templating
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
db = DatabaseConnect()
|
db = DatabaseConnect()
|
||||||
|
|
||||||
|
Flask.jinja_options["loader"] = FileSystemLoader("web")
|
||||||
|
env = app.create_jinja_environment()
|
||||||
|
|
||||||
|
DISPLAYEDTYPES = 2
|
||||||
|
TDLATE = datetime.timedelta(hours=8)
|
||||||
|
|
||||||
|
# before and after
|
||||||
|
TYPEUNITS = [
|
||||||
|
"°C",
|
||||||
|
"%"
|
||||||
|
]
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def index():
|
def index():
|
||||||
avail_rooms = db.view_valid_rooms(1)
|
# no user handling yet so we get user of ID 1 (not logged in)
|
||||||
outtext = "<h1>Available rooms</h1><ul>"
|
active_user = 1
|
||||||
if len(avail_rooms) == 0:
|
#populate userinfo
|
||||||
outtext += "<li>You have no rooms you can view</li>"
|
user_info = templating.UserInfo(id=active_user, name=db.username_from_id(active_user)[0])
|
||||||
else:
|
|
||||||
for room in avail_rooms:
|
rooms_info = []
|
||||||
outtext += f"<li><a href=/room/{room[1]}>{room[0]}</li>"
|
for room in db.view_valid_rooms(active_user):
|
||||||
outtext += "</ul>"
|
sensor_info = []
|
||||||
return outtext
|
sensors_avail = db.get_sensors_in_room_id(room[2])
|
||||||
|
for i in range(DISPLAYEDTYPES):
|
||||||
|
sensors_with_type = [x for x in sensors_avail if x[1] == i]
|
||||||
|
# TODO: handle more than one sensor in one room
|
||||||
|
if sensors_with_type:
|
||||||
|
reading = db.get_sensorsinfo_by_sensorid(sensors_with_type[0][0])
|
||||||
|
if reading is None:
|
||||||
|
sensor_info.append(templating.SensorInfo(2,None, None, ""))
|
||||||
|
continue
|
||||||
|
reading = templating.SensorInfo(1 if reading.timestamp + TDLATE < datetime.datetime.now() else 0,
|
||||||
|
reading.type,
|
||||||
|
reading.timestamp,
|
||||||
|
f"{reading.reading}{TYPEUNITS[i]}")
|
||||||
|
sensor_info.append(reading)
|
||||||
|
else:
|
||||||
|
sensor_info.append(templating.SensorInfo(2,None, None, ""))
|
||||||
|
rooms_info.append(templating.RoomInfo(room[0], room[1], sensor_info))
|
||||||
|
|
||||||
|
out_html = templating.LandingPage(env, user_info,rooms_info)
|
||||||
|
return out_html.render()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/room/<room_name>')
|
@app.route('/room/<room_name>')
|
||||||
def room_page(room_name=None):
|
def room_page(room_name=None):
|
||||||
if not db.user_has_room_perms(1,room_name):
|
if not db.user_has_room_perms(1,room_name):
|
||||||
abort(403)
|
abort(403)
|
||||||
|
|
||||||
sensor_list = db.get_sensors_in_room(room_name)
|
sensor_list = db.get_sensors_in_room_shortname(room_name)
|
||||||
fig = make_subplots(rows=1, cols=len(sensor_list))
|
fig = make_subplots(rows=1, cols=len(sensor_list))
|
||||||
for idx, sensorID in enumerate(sensor_list):
|
for idx, sensorID in enumerate(sensor_list):
|
||||||
lst = [x for x in db.get_sensor_data(sensorID)]
|
lst = [x for x in db.get_sensor_data(sensorID)]
|
||||||
@@ -47,4 +83,7 @@ def room_page(room_name=None):
|
|||||||
px_jinja_data = {"fig":fig.to_html(full_html=False)}
|
px_jinja_data = {"fig":fig.to_html(full_html=False)}
|
||||||
with open(template_path,'r') as template_file:
|
with open(template_path,'r') as template_file:
|
||||||
j2_template = Template(template_file.read())
|
j2_template = Template(template_file.read())
|
||||||
return j2_template.render(px_jinja_data)
|
return j2_template.render(px_jinja_data)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
36
web/base.html.jinja
Normal file
36
web/base.html.jinja
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=\, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css')}}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
|
||||||
|
<script src="{{ url_for('static', filename='scripts.js') }}"></script>
|
||||||
|
<title>{{ pagetitle }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="main-flex">
|
||||||
|
<div class="navbar">
|
||||||
|
<div class="topbranding">{{ top_branding }}</div>
|
||||||
|
<div class="navbar-inner">
|
||||||
|
<b><a href="index.html">Home</a></b><br>
|
||||||
|
<i class="bi bi-arrow-down-square-fill"> </i>Rooms<br>
|
||||||
|
<hr>
|
||||||
|
<a href="device.html">Device Management</a><br>
|
||||||
|
<a href="users.html">User Management</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="right-flex">
|
||||||
|
<div class="topline">
|
||||||
|
<div class="user section"> {{ userinfo.name }}</div>
|
||||||
|
<div class="logout section"> <a href="logout">
|
||||||
|
Logout <i class="bi bi-power"></i>
|
||||||
|
</a></div>
|
||||||
|
</div>
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -26,12 +26,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<title>{{ Pagetitle }}</title>
|
<title>{{ pagetitle }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="main">
|
<div class="main-flex">
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<div class="topbranding">{{ topbranding }}</div>
|
<div class="topbranding">{{ top_branding }}</div>
|
||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<b><a href="index.html">Home</a></b><br>
|
<b><a href="index.html">Home</a></b><br>
|
||||||
<i class="bi bi-arrow-down-square-fill"> </i>Rooms<br>
|
<i class="bi bi-arrow-down-square-fill"> </i>Rooms<br>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<a href="users.html">User Management</a>
|
<a href="users.html">User Management</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="right-flex">
|
||||||
<div class="topline">
|
<div class="topline">
|
||||||
|
|
||||||
<div class="user section"> {{ user }}</div>
|
<div class="user section"> {{ user }}</div>
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
Logout <i class="bi bi-power"></i>
|
Logout <i class="bi bi-power"></i>
|
||||||
</a></div>
|
</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="content">
|
||||||
<p> {{ user_greeting }}</p>
|
<p> {{ user_greeting }}</p>
|
||||||
<p> {{ ladning_information }}</p>
|
<p> {{ ladning_information }}</p>
|
||||||
<div class="rooms-table">
|
<div class="rooms-table">
|
||||||
|
|||||||
43
web/landing.html.jinja
Normal file
43
web/landing.html.jinja
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% extends "base.html.jinja" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="content">
|
||||||
|
<p> {{ user_greeting_before }}{{userinfo.name}}{{user_greeting_after}}</p>
|
||||||
|
<p> {{ landing_information }} </p>
|
||||||
|
<div class="rooms-table">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr class="table-header">
|
||||||
|
<th class="room-table-elem">Room name:</th>
|
||||||
|
<th>Temperature</th>
|
||||||
|
<th>Humidity</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="searchbar">
|
||||||
|
<td colspan="255">
|
||||||
|
<i class="bi bi-search"></i>
|
||||||
|
<input type="text" id="tableRoomSearch" placeholder="Search by room name..." onkeyup="tableSearch()">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tableRooms">
|
||||||
|
{% for room in roominfo %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="./room/{{ room.shortcode }}">{{ room.name }}</a>
|
||||||
|
{%- for sensorinfo in room.roomdata %}
|
||||||
|
{% if sensorinfo.state == 0 %}
|
||||||
|
<td class="data-valid">
|
||||||
|
{% elif sensorinfo.state == 1 %}
|
||||||
|
<td class="data-late">
|
||||||
|
{% else %}
|
||||||
|
<td class="data-missing">
|
||||||
|
{% endif %}
|
||||||
|
{{ sensorinfo.reading }}
|
||||||
|
</td>
|
||||||
|
{%- endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
Reference in New Issue
Block a user