Разработаны модули извлечения и сохранения статистических данных, разработан набросок веб-приложения
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