Разработаны модули извлечения и сохранения статистических данных, разработан набросок веб-приложения
This commit is contained in:
BIN
OpenHardwareMonitorLib.dll
Normal file
BIN
OpenHardwareMonitorLib.dll
Normal file
Binary file not shown.
1
database/__init__.py
Normal file
1
database/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .main import connect, get_sensors, get_sensor_values
|
190
database/main.py
Normal file
190
database/main.py
Normal file
@ -0,0 +1,190 @@
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from models import HardwareType, Hardware, SensorType, Sensor, SensorValue
|
||||
|
||||
|
||||
directory_path = os.path.join(
|
||||
os.getenv('appdata'),
|
||||
'csasq',
|
||||
'Hardware Monitor',
|
||||
)
|
||||
file_path = os.path.join(
|
||||
directory_path,
|
||||
'data.sql',
|
||||
)
|
||||
|
||||
|
||||
def connect():
|
||||
os.makedirs(
|
||||
name=directory_path,
|
||||
exist_ok=True,
|
||||
)
|
||||
return sqlite3.connect(
|
||||
database=file_path,
|
||||
)
|
||||
|
||||
|
||||
with connect() as connection:
|
||||
cursor = connection.cursor()
|
||||
sql = '''
|
||||
create table if not exists settings (
|
||||
name,
|
||||
value,
|
||||
primary key (name)
|
||||
);
|
||||
|
||||
create table if not exists hardware_types (
|
||||
id integer,
|
||||
name,
|
||||
primary key (id),
|
||||
unique (name)
|
||||
);
|
||||
|
||||
create table if not exists hardware (
|
||||
id integer,
|
||||
identifier,
|
||||
name,
|
||||
hardware_type_id,
|
||||
primary key (id),
|
||||
foreign key (hardware_type_id) references hardware_types on delete cascade on update cascade,
|
||||
unique (identifier)
|
||||
);
|
||||
|
||||
create table if not exists sensor_types (
|
||||
id integer,
|
||||
name,
|
||||
primary key (id),
|
||||
unique (name)
|
||||
);
|
||||
|
||||
create table if not exists sensors (
|
||||
id integer,
|
||||
hardware_id,
|
||||
sensor_type_id,
|
||||
identifier,
|
||||
name,
|
||||
'index',
|
||||
is_default_hidden,
|
||||
enabled,
|
||||
primary key (id),
|
||||
foreign key (hardware_id) references hardware on delete cascade on update cascade,
|
||||
foreign key (sensor_type_id) references sensor_types on delete cascade on update cascade,
|
||||
unique (identifier, sensor_type_id)
|
||||
);
|
||||
|
||||
create table if not exists sensor_values (
|
||||
sensor_id,
|
||||
timestamp,
|
||||
value,
|
||||
foreign key (sensor_id) references sensors
|
||||
);
|
||||
|
||||
pragma foreign_keys = on;
|
||||
'''
|
||||
cursor.executescript(sql)
|
||||
|
||||
|
||||
def get_sensors() -> list[Sensor]:
|
||||
with connect() as connection:
|
||||
cursor = connection.cursor()
|
||||
sql = '''
|
||||
select
|
||||
sensors.id,
|
||||
hardware.id,
|
||||
hardware.identifier,
|
||||
hardware.name,
|
||||
hardware_types.id,
|
||||
hardware_types.name,
|
||||
sensor_types.id,
|
||||
sensor_types.name,
|
||||
sensors.identifier,
|
||||
sensors.name,
|
||||
sensors.'index',
|
||||
sensors.is_default_hidden,
|
||||
sensors.enabled
|
||||
from
|
||||
sensors
|
||||
left outer join hardware on
|
||||
sensors.hardware_id = hardware.id
|
||||
left outer join hardware_types on
|
||||
hardware.hardware_type_id = hardware_types.id
|
||||
left outer join sensor_types on
|
||||
sensors.sensor_type_id = sensor_types.id;
|
||||
'''
|
||||
cursor.execute(sql)
|
||||
records = cursor.fetchall()
|
||||
return list(
|
||||
Sensor(
|
||||
id=sensor_id,
|
||||
hardware=Hardware(
|
||||
id=sensor_hardware_id,
|
||||
identifier=sensor_hardware_identifier,
|
||||
name=sensor_hardware_name,
|
||||
hardware_type=HardwareType(
|
||||
id=sensor_hardware_type_id,
|
||||
name=sensor_hardware_type_name,
|
||||
),
|
||||
),
|
||||
sensor_type=SensorType(
|
||||
id=sensor_type_id,
|
||||
name=sensor_type_name,
|
||||
),
|
||||
identifier=sensor_identifier,
|
||||
name=sensor_name,
|
||||
index=sensor_index,
|
||||
is_default_hidden=sensor_is_default_hidden,
|
||||
enabled=sensor_enabled,
|
||||
)
|
||||
for
|
||||
sensor_id,
|
||||
sensor_hardware_id,
|
||||
sensor_hardware_identifier,
|
||||
sensor_hardware_name,
|
||||
sensor_hardware_type_id,
|
||||
sensor_hardware_type_name,
|
||||
sensor_type_id,
|
||||
sensor_type_name,
|
||||
sensor_identifier,
|
||||
sensor_name,
|
||||
sensor_index,
|
||||
sensor_is_default_hidden,
|
||||
sensor_enabled,
|
||||
in
|
||||
records
|
||||
)
|
||||
|
||||
|
||||
def get_sensor_values() -> list[SensorValue]:
|
||||
with connect() as connection:
|
||||
cursor = connection.cursor()
|
||||
sql = '''
|
||||
select
|
||||
sensor_values.sensor_id,
|
||||
sensor_values.timestamp,
|
||||
sensor_values.value
|
||||
from
|
||||
sensor_values
|
||||
where
|
||||
sensor_values.sensor_id in ?;
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
[15, 16, 17],
|
||||
),
|
||||
)
|
||||
records, = cursor.fetchall()
|
||||
return list(
|
||||
SensorValue(
|
||||
sensor_id=sensor_id,
|
||||
timestamp=timestamp,
|
||||
value=value,
|
||||
)
|
||||
for
|
||||
sensor_id,
|
||||
timestamp,
|
||||
value,
|
||||
in
|
||||
records
|
||||
)
|
15
main.py
Normal file
15
main.py
Normal file
@ -0,0 +1,15 @@
|
||||
from monitor import Monitor
|
||||
from threading import Thread
|
||||
import uvicorn
|
||||
from web import app
|
||||
|
||||
|
||||
Monitor().start()
|
||||
web_thread = Thread(
|
||||
target=uvicorn.run(
|
||||
app=app,
|
||||
host='localhost',
|
||||
port=8000,
|
||||
),
|
||||
)
|
||||
web_thread.start()
|
1
models/__init__.py
Normal file
1
models/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .main import Setting, HardwareType, Hardware, SensorType, Sensor, SensorValue
|
53
models/main.py
Normal file
53
models/main.py
Normal file
@ -0,0 +1,53 @@
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Setting(BaseModel):
|
||||
name: str
|
||||
value: str
|
||||
|
||||
|
||||
class HardwareType(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class Hardware(BaseModel):
|
||||
id: int
|
||||
identifier: str
|
||||
name: str
|
||||
hardware_type: HardwareType
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class SensorType(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class Sensor(BaseModel):
|
||||
id: int
|
||||
hardware: Hardware
|
||||
sensor_type: SensorType
|
||||
identifier: str
|
||||
name: str
|
||||
index: int
|
||||
is_default_hidden: bool
|
||||
enabled: bool
|
||||
|
||||
def __hash__(self):
|
||||
return self.id
|
||||
|
||||
|
||||
class SensorValue(BaseModel):
|
||||
sensor: Sensor
|
||||
timestamp: datetime
|
||||
value: str
|
1
monitor/__init__.py
Normal file
1
monitor/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .main import Monitor
|
159
monitor/main.py
Normal file
159
monitor/main.py
Normal file
@ -0,0 +1,159 @@
|
||||
import clr
|
||||
from datetime import datetime
|
||||
import os
|
||||
from threading import Thread
|
||||
import time
|
||||
|
||||
import database
|
||||
|
||||
|
||||
class Monitor(Thread):
|
||||
def __init__(self):
|
||||
clr.AddReference(r'OpenHardwareMonitorLib')
|
||||
from OpenHardwareMonitor import Hardware
|
||||
self.handle = Hardware.Computer()
|
||||
self.handle.MainboardEnabled = True
|
||||
self.handle.CPUEnabled = True
|
||||
self.handle.RAMEnabled = True
|
||||
self.handle.GPUEnabled = True
|
||||
self.handle.HDDEnabled = True
|
||||
self.handle.Open()
|
||||
super().__init__()
|
||||
|
||||
def run(self):
|
||||
def parse_sensor(sensor):
|
||||
sql = '''
|
||||
insert into sensor_types (
|
||||
name
|
||||
) values (
|
||||
?
|
||||
)
|
||||
on conflict (name) do update set
|
||||
name = sensor_types.name
|
||||
returning
|
||||
sensor_types.id;
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
str(sensor.SensorType),
|
||||
),
|
||||
)
|
||||
sensor_type_id, = cursor.fetchone()
|
||||
sql = '''
|
||||
insert into sensors (
|
||||
hardware_id,
|
||||
sensor_type_id,
|
||||
identifier,
|
||||
name,
|
||||
'index',
|
||||
is_default_hidden,
|
||||
enabled
|
||||
) values (
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
false
|
||||
)
|
||||
on conflict (identifier, sensor_type_id) do update set
|
||||
hardware_id = sensors.hardware_id,
|
||||
name = sensors.name,
|
||||
'index' = sensors.'index',
|
||||
is_default_hidden = sensors.is_default_hidden,
|
||||
enabled = sensors.enabled
|
||||
returning
|
||||
sensors.id,
|
||||
sensors.enabled;
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
str(hardware_id),
|
||||
sensor_type_id,
|
||||
str(sensor.Identifier),
|
||||
str(sensor.Name),
|
||||
int(sensor.Index),
|
||||
bool(sensor.IsDefaultHidden),
|
||||
),
|
||||
)
|
||||
sensor_id, sensor_enabled, = cursor.fetchone()
|
||||
if not sensor_enabled:
|
||||
return
|
||||
sql = '''
|
||||
insert into sensor_values (
|
||||
sensor_id,
|
||||
timestamp,
|
||||
value
|
||||
) values (
|
||||
?,
|
||||
?,
|
||||
?
|
||||
);
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
sensor_id,
|
||||
datetime.now(),
|
||||
sensor.Value,
|
||||
),
|
||||
)
|
||||
|
||||
while True:
|
||||
for hardware in self.handle.Hardware:
|
||||
with database.connect() as connection:
|
||||
cursor = connection.cursor()
|
||||
sql = '''
|
||||
insert into hardware_types (
|
||||
name
|
||||
) values (
|
||||
?
|
||||
)
|
||||
on conflict (name) do update set
|
||||
name = hardware_types.name
|
||||
returning
|
||||
hardware_types.id;
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
str(hardware.HardwareType),
|
||||
),
|
||||
)
|
||||
hardware_type_id, = cursor.fetchone()
|
||||
sql = '''
|
||||
insert into hardware (
|
||||
identifier,
|
||||
name,
|
||||
hardware_type_id
|
||||
) values (
|
||||
?,
|
||||
?,
|
||||
?
|
||||
)
|
||||
on conflict (identifier) do update set
|
||||
name = hardware.name,
|
||||
hardware_type_id = hardware.hardware_type_id
|
||||
returning
|
||||
hardware.id;
|
||||
'''
|
||||
cursor.execute(
|
||||
sql,
|
||||
(
|
||||
str(hardware.Identifier),
|
||||
str(hardware.Name),
|
||||
hardware_type_id,
|
||||
),
|
||||
)
|
||||
hardware_id, = cursor.fetchone()
|
||||
hardware.Update()
|
||||
for sensor in hardware.Sensors:
|
||||
parse_sensor(sensor)
|
||||
for subhardware in hardware.SubHardware:
|
||||
subhardware.Update()
|
||||
for subsensor in subhardware.Sensors:
|
||||
parse_sensor(subsensor)
|
||||
time.sleep(1)
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
||||
fastapi
|
||||
# gunicorn
|
||||
jinja2
|
||||
pydantic
|
||||
pythonnet
|
||||
uvicorn
|
276
templates/main.jinja2
Normal file
276
templates/main.jinja2
Normal file
@ -0,0 +1,276 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru" data-bs-theme="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title></title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.min.css" integrity="sha256-BicZsQAhkGHIoR//IB2amPN5SrRb3fHB8tFsnqRAwnk=" crossorigin="anonymous" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.0/dist/chart.umd.min.js"></script>
|
||||
<style>
|
||||
html {
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
|
||||
body,
|
||||
main {
|
||||
min-height: 100% !important;
|
||||
}
|
||||
|
||||
svg > symbol {
|
||||
fill: silver;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.sidebar .offcanvas-lg {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
.navbar-search {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #2470dc;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
|
||||
.navbar-brand {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
.navbar .form-control {
|
||||
padding: .75rem 1rem;
|
||||
}
|
||||
</style>
|
||||
<script >
|
||||
const api_url = new URL(location.origin);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="check2" viewBox="0 0 16 16">
|
||||
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="circle-half" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z"></path>
|
||||
</symbol>
|
||||
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
|
||||
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path>
|
||||
<path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"></path>
|
||||
</symbol>
|
||||
<symbol id="sun-fill" viewBox="0 0 16 16">
|
||||
<path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="calendar3" viewBox="0 0 16 16">
|
||||
<path d="M14 0H2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zM1 3.857C1 3.384 1.448 3 2 3h12c.552 0 1 .384 1 .857v10.286c0 .473-.448.857-1 .857H2c-.552 0-1-.384-1-.857V3.857z"></path>
|
||||
<path d="M6.5 7a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm-9 3a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm3 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"></path>
|
||||
</symbol>
|
||||
<symbol id="cart" viewBox="0 0 16 16">
|
||||
<path d="M0 1.5A.5.5 0 0 1 .5 1H2a.5.5 0 0 1 .485.379L2.89 3H14.5a.5.5 0 0 1 .49.598l-1 5a.5.5 0 0 1-.465.401l-9.397.472L4.415 11H13a.5.5 0 0 1 0 1H4a.5.5 0 0 1-.491-.408L2.01 3.607 1.61 2H.5a.5.5 0 0 1-.5-.5zM3.102 4l.84 4.479 9.144-.459L13.89 4H3.102zM5 12a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm7 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm-7 1a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm7 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="chevron-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"></path>
|
||||
</symbol>
|
||||
<symbol id="door-closed" viewBox="0 0 16 16">
|
||||
<path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3V2zm1 13h8V2H4v13z"></path>
|
||||
<path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="file-earmark" viewBox="0 0 16 16">
|
||||
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"></path>
|
||||
</symbol>
|
||||
<symbol id="file-earmark-text" viewBox="0 0 16 16">
|
||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1h-5zM5 9.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5zm0 2a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5z"></path>
|
||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0zm0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="gear-wide-connected" viewBox="0 0 16 16">
|
||||
<path d="M7.068.727c.243-.97 1.62-.97 1.864 0l.071.286a.96.96 0 0 0 1.622.434l.205-.211c.695-.719 1.888-.03 1.613.931l-.08.284a.96.96 0 0 0 1.187 1.187l.283-.081c.96-.275 1.65.918.931 1.613l-.211.205a.96.96 0 0 0 .434 1.622l.286.071c.97.243.97 1.62 0 1.864l-.286.071a.96.96 0 0 0-.434 1.622l.211.205c.719.695.03 1.888-.931 1.613l-.284-.08a.96.96 0 0 0-1.187 1.187l.081.283c.275.96-.918 1.65-1.613.931l-.205-.211a.96.96 0 0 0-1.622.434l-.071.286c-.243.97-1.62.97-1.864 0l-.071-.286a.96.96 0 0 0-1.622-.434l-.205.211c-.695.719-1.888.03-1.613-.931l.08-.284a.96.96 0 0 0-1.186-1.187l-.284.081c-.96.275-1.65-.918-.931-1.613l.211-.205a.96.96 0 0 0-.434-1.622l-.286-.071c-.97-.243-.97-1.62 0-1.864l.286-.071a.96.96 0 0 0 .434-1.622l-.211-.205c-.719-.695-.03-1.888.931-1.613l.284.08a.96.96 0 0 0 1.187-1.186l-.081-.284c-.275-.96.918-1.65 1.613-.931l.205.211a.96.96 0 0 0 1.622-.434l.071-.286zM12.973 8.5H8.25l-2.834 3.779A4.998 4.998 0 0 0 12.973 8.5zm0-1a4.998 4.998 0 0 0-7.557-3.779l2.834 3.78h4.723zM5.048 3.967c-.03.021-.058.043-.087.065l.087-.065zm-.431.355A4.984 4.984 0 0 0 3.002 8c0 1.455.622 2.765 1.615 3.678L7.375 8 4.617 4.322zm.344 7.646.087.065-.087-.065z"></path>
|
||||
</symbol>
|
||||
<symbol id="graph-up" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd"
|
||||
d="M0 0h1v15h15v1H0V0Zm14.817 3.113a.5.5 0 0 1 .07.704l-4.5 5.5a.5.5 0 0 1-.74.037L7.06 6.767l-3.656 5.027a.5.5 0 0 1-.808-.588l4-5.5a.5.5 0 0 1 .758-.06l2.609 2.61 4.15-5.073a.5.5 0 0 1 .704-.07Z"></path>
|
||||
</symbol>
|
||||
<symbol id="house-fill" viewBox="0 0 16 16">
|
||||
<path d="M8.707 1.5a1 1 0 0 0-1.414 0L.646 8.146a.5.5 0 0 0 .708.708L8 2.207l6.646 6.647a.5.5 0 0 0 .708-.708L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.707 1.5Z"></path>
|
||||
<path d="m8 3.293 6 6V13.5a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 2 13.5V9.293l6-6Z"></path>
|
||||
</symbol>
|
||||
<symbol id="list" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd"
|
||||
d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="people" viewBox="0 0 16 16">
|
||||
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8Zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022ZM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4Zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816ZM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0Zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z"></path>
|
||||
</symbol>
|
||||
<symbol id="plus-circle" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
|
||||
</symbol>
|
||||
<symbol id="puzzle" viewBox="0 0 16 16">
|
||||
<path d="M3.112 3.645A1.5 1.5 0 0 1 4.605 2H7a.5.5 0 0 1 .5.5v.382c0 .696-.497 1.182-.872 1.469a.459.459 0 0 0-.115.118.113.113 0 0 0-.012.025L6.5 4.5v.003l.003.01c.004.01.014.028.036.053a.86.86 0 0 0 .27.194C7.09 4.9 7.51 5 8 5c.492 0 .912-.1 1.19-.24a.86.86 0 0 0 .271-.194.213.213 0 0 0 .039-.063v-.009a.112.112 0 0 0-.012-.025.459.459 0 0 0-.115-.118c-.375-.287-.872-.773-.872-1.469V2.5A.5.5 0 0 1 9 2h2.395a1.5 1.5 0 0 1 1.493 1.645L12.645 6.5h.237c.195 0 .42-.147.675-.48.21-.274.528-.52.943-.52.568 0 .947.447 1.154.862C15.877 6.807 16 7.387 16 8s-.123 1.193-.346 1.638c-.207.415-.586.862-1.154.862-.415 0-.733-.246-.943-.52-.255-.333-.48-.48-.675-.48h-.237l.243 2.855A1.5 1.5 0 0 1 11.395 14H9a.5.5 0 0 1-.5-.5v-.382c0-.696.497-1.182.872-1.469a.459.459 0 0 0 .115-.118.113.113 0 0 0 .012-.025L9.5 11.5v-.003a.214.214 0 0 0-.039-.064.859.859 0 0 0-.27-.193C8.91 11.1 8.49 11 8 11c-.491 0-.912.1-1.19.24a.859.859 0 0 0-.271.194.214.214 0 0 0-.039.063v.003l.001.006a.113.113 0 0 0 .012.025c.016.027.05.068.115.118.375.287.872.773.872 1.469v.382a.5.5 0 0 1-.5.5H4.605a1.5 1.5 0 0 1-1.493-1.645L3.356 9.5h-.238c-.195 0-.42.147-.675.48-.21.274-.528.52-.943.52-.568 0-.947-.447-1.154-.862C.123 9.193 0 8.613 0 8s.123-1.193.346-1.638C.553 5.947.932 5.5 1.5 5.5c.415 0 .733.246.943.52.255.333.48.48.675.48h.238l-.244-2.855zM4.605 3a.5.5 0 0 0-.498.55l.001.007.29 3.4A.5.5 0 0 1 3.9 7.5h-.782c-.696 0-1.182-.497-1.469-.872a.459.459 0 0 0-.118-.115.112.112 0 0 0-.025-.012L1.5 6.5h-.003a.213.213 0 0 0-.064.039.86.86 0 0 0-.193.27C1.1 7.09 1 7.51 1 8c0 .491.1.912.24 1.19.07.14.14.225.194.271a.213.213 0 0 0 .063.039H1.5l.006-.001a.112.112 0 0 0 .025-.012.459.459 0 0 0 .118-.115c.287-.375.773-.872 1.469-.872H3.9a.5.5 0 0 1 .498.542l-.29 3.408a.5.5 0 0 0 .497.55h1.878c-.048-.166-.195-.352-.463-.557-.274-.21-.52-.528-.52-.943 0-.568.447-.947.862-1.154C6.807 10.123 7.387 10 8 10s1.193.123 1.638.346c.415.207.862.586.862 1.154 0 .415-.246.733-.52.943-.268.205-.415.39-.463.557h1.878a.5.5 0 0 0 .498-.55l-.001-.007-.29-3.4A.5.5 0 0 1 12.1 8.5h.782c.696 0 1.182.497 1.469.872.05.065.091.099.118.115.013.008.021.01.025.012a.02.02 0 0 0 .006.001h.003a.214.214 0 0 0 .064-.039.86.86 0 0 0 .193-.27c.14-.28.24-.7.24-1.191 0-.492-.1-.912-.24-1.19a.86.86 0 0 0-.194-.271.215.215 0 0 0-.063-.039H14.5l-.006.001a.113.113 0 0 0-.025.012.459.459 0 0 0-.118.115c-.287.375-.773.872-1.469.872H12.1a.5.5 0 0 1-.498-.543l.29-3.407a.5.5 0 0 0-.497-.55H9.517c.048.166.195.352.463.557.274.21.52.528.52.943 0 .568-.447.947-.862 1.154C9.193 5.877 8.613 6 8 6s-1.193-.123-1.638-.346C5.947 5.447 5.5 5.068 5.5 4.5c0-.415.246-.733.52-.943.268-.205.415-.39.463-.557H4.605z"></path>
|
||||
</symbol>
|
||||
<symbol id="search" viewBox="0 0 16 16">
|
||||
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"></path>
|
||||
</symbol>
|
||||
<symbol id="cpu-icon" viewBox="0 0 16 16">
|
||||
<path d="M5 0a.5.5 0 0 1 .5.5V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2h1V.5a.5.5 0 0 1 1 0V2A2.5 2.5 0 0 1 14 4.5h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14v1h1.5a.5.5 0 0 1 0 1H14a2.5 2.5 0 0 1-2.5 2.5v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14h-1v1.5a.5.5 0 0 1-1 0V14A2.5 2.5 0 0 1 2 11.5H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2v-1H.5a.5.5 0 0 1 0-1H2A2.5 2.5 0 0 1 4.5 2V.5A.5.5 0 0 1 5 0zm-.5 3A1.5 1.5 0 0 0 3 4.5v7A1.5 1.5 0 0 0 4.5 13h7a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 11.5 3h-7zM5 6.5A1.5 1.5 0 0 1 6.5 5h3A1.5 1.5 0 0 1 11 6.5v3A1.5 1.5 0 0 1 9.5 11h-3A1.5 1.5 0 0 1 5 9.5v-3zM6.5 6a.5.5 0 0 0-.5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 0-.5-.5h-3z"></path>
|
||||
</symbol>
|
||||
<symbol id="gpu-icon" viewBox="0 0 16 16">
|
||||
<path d="M4 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm7.5-1.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z"></path>
|
||||
<path d="M0 1.5A.5.5 0 0 1 .5 1h1a.5.5 0 0 1 .5.5V4h13.5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5H2v2.5a.5.5 0 0 1-1 0V2H.5a.5.5 0 0 1-.5-.5Zm5.5 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM9 8a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0Z"></path>
|
||||
<path d="M3 12.5h3.5v1a.5.5 0 0 1-.5.5H3.5a.5.5 0 0 1-.5-.5v-1Zm4 1v-1h4v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5Z"></path>
|
||||
</symbol>
|
||||
<symbol id="ram-icon" viewBox="0 0 16 16">
|
||||
<path d="M1 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h4.586a1 1 0 0 0 .707-.293l.353-.353a.5.5 0 0 1 .708 0l.353.353a1 1 0 0 0 .707.293H15a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1H1Zm.5 1h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5Zm5 0h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5Zm4.5.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-4ZM2 10v2H1v-2h1Zm2 0v2H3v-2h1Zm2 0v2H5v-2h1Zm3 0v2H8v-2h1Zm2 0v2h-1v-2h1Zm2 0v2h-1v-2h1Zm2 0v2h-1v-2h1Z"></path>
|
||||
</symbol>
|
||||
<symbol id="rom-icon" viewBox="0 0 16 16">
|
||||
<path d="M4.5 11a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1zM3 10.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"></path>
|
||||
<path d="M16 11a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V9.51c0-.418.105-.83.305-1.197l2.472-4.531A1.5 1.5 0 0 1 4.094 3h7.812a1.5 1.5 0 0 1 1.317.782l2.472 4.53c.2.368.305.78.305 1.198V11zM3.655 4.26 1.592 8.043C1.724 8.014 1.86 8 2 8h12c.14 0 .276.014.408.042L12.345 4.26a.5.5 0 0 0-.439-.26H4.094a.5.5 0 0 0-.44.26zM1 10v1a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-1a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1z"></path>
|
||||
</symbol>
|
||||
<symbol id="mainboard-icon" viewBox="0 0 16 16">
|
||||
<path d="M11.5 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5Zm2 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5Zm-10 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6Zm0 2a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1h-6ZM5 3a1 1 0 0 0-1 1h-.5a.5.5 0 0 0 0 1H4v1h-.5a.5.5 0 0 0 0 1H4a1 1 0 0 0 1 1v.5a.5.5 0 0 0 1 0V8h1v.5a.5.5 0 0 0 1 0V8a1 1 0 0 0 1-1h.5a.5.5 0 0 0 0-1H9V5h.5a.5.5 0 0 0 0-1H9a1 1 0 0 0-1-1v-.5a.5.5 0 0 0-1 0V3H6v-.5a.5.5 0 0 0-1 0V3Zm0 1h3v3H5V4Zm6.5 7a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-2Z"></path>
|
||||
<path d="M1 2a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-2H.5a.5.5 0 0 1-.5-.5v-1A.5.5 0 0 1 .5 9H1V8H.5a.5.5 0 0 1-.5-.5v-1A.5.5 0 0 1 .5 6H1V5H.5a.5.5 0 0 1-.5-.5v-2A.5.5 0 0 1 .5 2H1Zm1 11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v11Z"></path>
|
||||
</symbol>
|
||||
</svg>
|
||||
<header class="navbar sticky-top bg-dark flex-md-nowrap p-0 shadow">
|
||||
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6 text-white" href="#">Hardware Monitor</a>
|
||||
|
||||
<ul class="navbar-nav flex-row d-md-none">
|
||||
<li class="nav-item text-nowrap">
|
||||
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarSearch" aria-controls="navbarSearch" aria-expanded="false"
|
||||
aria-label="Toggle search">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#search"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item text-nowrap">
|
||||
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="offcanvas"
|
||||
data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false"
|
||||
aria-label="Toggle navigation">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#list"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="navbarSearch" class="navbar-search w-100 collapse">
|
||||
<input class="form-control w-100 rounded-0 border-0" type="text" placeholder="Search" aria-label="Search">
|
||||
</div>
|
||||
</header>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="sidebar border border-right col-md-3 col-lg-2 p-0 bg-body-tertiary">
|
||||
<div class="offcanvas-lg offcanvas-end bg-body-tertiary" tabindex="-1" id="sidebarMenu"
|
||||
aria-labelledby="sidebarMenuLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="sidebarMenuLabel">Hardware Monitor</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#sidebarMenu" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body d-md-flex flex-column p-0 pt-lg-3 pb-lg-3 overflow-y-auto">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2 active" aria-current="page" href="/reports/mainboard">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#mainboard-icon"></use>
|
||||
</svg>
|
||||
Mainboard
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" aria-current="page" href="/reports/cpu">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#cpu-icon"></use>
|
||||
</svg>
|
||||
CPU
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" aria-current="page" href="/reports/gpu">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#gpu-icon"></use>
|
||||
</svg>
|
||||
GPU
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" aria-current="page" href="/reports/ram">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#ram-icon"></use>
|
||||
</svg>
|
||||
RAM
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" aria-current="page" href="/reports/rom">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#rom-icon"></use>
|
||||
</svg>
|
||||
ROM
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<ul class="nav flex-column mb-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link d-flex align-items-center gap-2" href="/settings">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#gear-wide-connected"></use>
|
||||
</svg>
|
||||
Настройки
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
{% block main %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
99
templates/reports.jinja2
Normal file
99
templates/reports.jinja2
Normal file
@ -0,0 +1,99 @@
|
||||
{% extends 'main.jinja2' %}
|
||||
{% block main %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Mainboard</h1>
|
||||
<div class="btn-toolbar mb-2 mb-md-0">
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
|
||||
</div>
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle d-flex align-items-center gap-1" data-bs-toggle="dropdown">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#calendar3"></use>
|
||||
</svg>
|
||||
Температура
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">День</a></li>
|
||||
<li><a class="dropdown-item" href="#">Неделя</a></li>
|
||||
<li><a class="dropdown-item" href="#">Месяц</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group me-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle d-flex align-items-center gap-1" data-bs-toggle="dropdown">
|
||||
<svg class="bi">
|
||||
<use xlink:href="#calendar3"></use>
|
||||
</svg>
|
||||
День
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" href="#">День</a></li>
|
||||
<li><a class="dropdown-item" href="#">Неделя</a></li>
|
||||
<li><a class="dropdown-item" href="#">Месяц</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas class="my-4 w-100" id="chart" width="1537" height="649" style="display: block; box-sizing: border-box; height: 649px; width: 1537px;"></canvas>
|
||||
<script>
|
||||
const canvas = document.querySelector('#chart');
|
||||
|
||||
const DATA_COUNT = 12;
|
||||
const labels = [];
|
||||
for (let i = 0; i < DATA_COUNT; ++i) {
|
||||
labels.push(i.toString());
|
||||
}
|
||||
const datapoints = [0, 20, 20, 60, 60, 120, 180, 120, 125, 105, 110, 170];
|
||||
const data = {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Cubic interpolation',
|
||||
data: datapoints,
|
||||
borderColor: 'blue',
|
||||
fill: false,
|
||||
tension: .4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
{#text: 'Chart.js Line Chart'#}
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: true,
|
||||
color: 'rgba(255, 255, 255, .2)',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: true,
|
||||
color: 'rgba(255, 255, 255, .2)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const chart = new Chart(
|
||||
canvas.getContext('2d'),
|
||||
config,
|
||||
);
|
||||
</script>
|
||||
{% endblock %}
|
79
templates/settings.jinja2
Normal file
79
templates/settings.jinja2
Normal file
@ -0,0 +1,79 @@
|
||||
{% extends 'main.jinja2' %}
|
||||
{% block main %}
|
||||
<div class="pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Настройки</h1>
|
||||
</div>
|
||||
{# <h1 class="h4">Панель управления</h1>#}
|
||||
{# <div class="input-group mb-3">#}
|
||||
{# <span class="input-group-text">http://</span>#}
|
||||
{# <input type="text" class="form-control" id="hostname" value="{{ settings['hostname'] }}" />#}
|
||||
{# <span class="input-group-text">:</span>#}
|
||||
{# <input type="number" class="form-control" id="port" min="1024" max="65535" value="{{ settings['port'] }}" />#}
|
||||
{# </div>#}
|
||||
<section>
|
||||
<h1 class="h4">Датчики</h1>
|
||||
<div id="sensors">
|
||||
{% for hardware_type in settings['hardware_types'] %}
|
||||
<div class="mb-3 form-check" data-hardware-type-id="{{ hardware_type.id }}">
|
||||
<input type="checkbox" class="form-check-input" id="hardware-type-{{ hardware_type.id }}" />
|
||||
<label class="form-check-label" for="hardware-type-{{ hardware_type.id }}">{{ hardware_type.name }}</label>
|
||||
</div>
|
||||
{% for hardware in settings['hardware_types'][hardware_type] %}
|
||||
<div class="ms-5">
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="hardware-{{ hardware.id }}" data-hardware-id="{{ hardware.id }}" />
|
||||
<label class="form-check-label" for="hardware-{{ hardware.id }}">{{ hardware.name }} <small class="text-secondary">{{ hardware.identifier }}</small></label>
|
||||
</div>
|
||||
{% for sensor_type in settings['hardware_types'][hardware_type][hardware] %}
|
||||
<div class="ms-5">
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="sensor-type-{{ sensor_type.id }}" data-sensor-type-id="{{ sensor_type.id }}" />
|
||||
<label class="form-check-label" for="sensor-type-{{ sensor_type.id }}">{{ sensor_type.name }}</label>
|
||||
</div>
|
||||
{% for sensor in settings['hardware_types'][hardware_type][hardware][sensor_type] %}
|
||||
<div class="ms-5">
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="sensor-{{ sensor.id }}" data-sensor-id="{{ sensor.id }}" {% if sensor.enabled %} checked {% endif %} />
|
||||
<label class="form-check-label" for="sensor-{{ sensor.id }}">{{ sensor.name }} <small class="text-secondary">{{ sensor.identifier }}</small></label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
<script>
|
||||
</script>
|
||||
{# <script>#}
|
||||
{# const $sensors = document.querySelector('#sensors');#}
|
||||
{# const xhr = new XMLHttpRequest();#}
|
||||
{# api_url.pathname = '/api/sensors';#}
|
||||
{# xhr.open('get', api_url);#}
|
||||
{# xhr.onload = () => {#}
|
||||
{# if (xhr.status !== 200) return;#}
|
||||
{# const sensors = JSON.parse(xhr.response);#}
|
||||
{# for (const sensor of sensors) {#}
|
||||
{# if ($sensors.querySelector(`[data-hardware-type-id="${ sensor.hardware.hardware_type.id }"]`) === null) {#}
|
||||
{# let $div = document.createElement('div');#}
|
||||
{# $div.classList.add('mb-3', 'form-check');#}
|
||||
{# let $input = document.createElement('input');#}
|
||||
{# $input.type = 'checkbox';#}
|
||||
{# $input.classList.add('form-check-input');#}
|
||||
{# $input.id = `hardware-type-${ sensor.hardware.hardware_type.id }`;#}
|
||||
{# $input.dataset.hardwareTypeId = String(sensor.hardware.hardware_type.id);#}
|
||||
{# let $label = document.createElement('label');#}
|
||||
{# $label.classList.add('form-check-label');#}
|
||||
{# $label.setAttribute('for', $input.id);#}
|
||||
{# $label.textContent = sensor.hardware.hardware_type.name;#}
|
||||
{# $div.append($input, $label);#}
|
||||
{# $sensors.append($div);#}
|
||||
{# }#}
|
||||
{# #}
|
||||
{# }#}
|
||||
{# };#}
|
||||
{# xhr.send();#}
|
||||
{# </script>#}
|
||||
{% endblock %}
|
1
web/__init__.py
Normal file
1
web/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .main import app
|
96
web/main.py
Normal file
96
web/main.py
Normal file
@ -0,0 +1,96 @@
|
||||
import asyncio
|
||||
from fastapi import FastAPI, Request, Header, Body, Path
|
||||
from fastapi.responses import Response, HTMLResponse, FileResponse, RedirectResponse, JSONResponse
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
import os
|
||||
|
||||
import database
|
||||
import models
|
||||
# from models import MailSizeData
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.mount(
|
||||
path='/static',
|
||||
app=StaticFiles(
|
||||
directory=os.path.join(
|
||||
os.getcwd(),
|
||||
'static',
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
env = Environment(
|
||||
loader=FileSystemLoader('./templates'),
|
||||
enable_async=True,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/')
|
||||
async def function():
|
||||
return RedirectResponse(
|
||||
url='/reports/mainboard',
|
||||
status_code=307,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/reports/mainboard')
|
||||
async def function():
|
||||
template = env.get_template('reports.jinja2')
|
||||
return HTMLResponse(
|
||||
content=await template.render_async(),
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/settings')
|
||||
async def function():
|
||||
hardware_types = dict()
|
||||
sensors = database.get_sensors()
|
||||
# hardware_types = set(
|
||||
# sensor.hardware.hardware_type
|
||||
# for sensor in sensors
|
||||
# )
|
||||
for hardware_type in set(
|
||||
sensor.hardware.hardware_type
|
||||
for sensor in sensors
|
||||
):
|
||||
hardware_types[hardware_type] = dict()
|
||||
for hardware in set(
|
||||
sensor.hardware
|
||||
for sensor in sensors
|
||||
if sensor.hardware.hardware_type == hardware_type
|
||||
):
|
||||
hardware_types[hardware_type][hardware] = dict()
|
||||
for sensor_type in set(
|
||||
sensor.sensor_type
|
||||
for sensor in sensors
|
||||
if sensor.hardware == hardware
|
||||
):
|
||||
hardware_types[hardware_type][hardware][sensor_type] = list()
|
||||
for sensor in sensors:
|
||||
if sensor.hardware == hardware and sensor.sensor_type == sensor_type:
|
||||
hardware_types[hardware_type][hardware][sensor_type].append(sensor)
|
||||
template = env.get_template('settings.jinja2')
|
||||
return HTMLResponse(
|
||||
content=await template.render_async(
|
||||
settings={
|
||||
'hostname': 'localhost',
|
||||
'port': 8000,
|
||||
'hardware_types': hardware_types,
|
||||
},
|
||||
),
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.get('/api/sensors')
|
||||
async def function():
|
||||
return database.get_sensors()
|
||||
|
||||
|
||||
@app.get('/api/sensor-values')
|
||||
async def function():
|
||||
return database.get_sensor_values()
|
Reference in New Issue
Block a user