initial commit

This commit is contained in:
2026-03-24 07:17:48 +01:00
commit 371cbce202
147 changed files with 18244 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,28 @@
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,92 @@
Metadata-Version: 2.1
Name: MarkupSafe
Version: 3.0.2
Summary: Safely add untrusted strings to HTML/XML markup.
Maintainer-email: Pallets <contact@palletsprojects.com>
License: Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
Project-URL: Source, https://github.com/pallets/markupsafe/
Project-URL: Chat, https://discord.gg/pallets
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Text Processing :: Markup :: HTML
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE.txt
# MarkupSafe
MarkupSafe implements a text object that escapes characters so it is
safe to use in HTML and XML. Characters that have special meanings are
replaced so that they display as the actual characters. This mitigates
injection attacks, meaning untrusted user input can safely be displayed
on a page.
## Examples
```pycon
>>> from markupsafe import Markup, escape
>>> # escape replaces special characters and wraps in Markup
>>> escape("<script>alert(document.cookie);</script>")
Markup('&lt;script&gt;alert(document.cookie);&lt;/script&gt;')
>>> # wrap in Markup to mark text "safe" and prevent escaping
>>> Markup("<strong>Hello</strong>")
Markup('<strong>hello</strong>')
>>> escape(Markup("<strong>Hello</strong>"))
Markup('<strong>hello</strong>')
>>> # Markup is a str subclass
>>> # methods and operators escape their arguments
>>> template = Markup("Hello <em>{name}</em>")
>>> template.format(name='"World"')
Markup('Hello <em>&#34;World&#34;</em>')
```
## Donate
The Pallets organization develops and supports MarkupSafe and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
[please donate today][].
[please donate today]: https://palletsprojects.com/donate

View File

@@ -0,0 +1,14 @@
MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=RjHsDbX9kKVH4zaBcmTGeYIUM4FG-KyUtKV_lu6MnsQ,1503
MarkupSafe-3.0.2.dist-info/METADATA,sha256=nhoabjupBG41j_JxPCJ3ylgrZ6Fx8oMCFbiLF9Kafqc,4067
MarkupSafe-3.0.2.dist-info/RECORD,,
MarkupSafe-3.0.2.dist-info/WHEEL,sha256=-v_yZ08fSknsoT62oIKG9wp1eCBV9_ao2rO4BeIReTY,101
MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
markupsafe/__init__.py,sha256=pREerPwvinB62tNCMOwqxBS2YHV6R52Wcq1d-rB4Z5o,13609
markupsafe/__pycache__/__init__.cpython-313.pyc,,
markupsafe/__pycache__/_native.cpython-313.pyc,,
markupsafe/_native.py,sha256=2ptkJ40yCcp9kq3L1NqpgjfpZB-obniYKFFKUOkHh4Q,218
markupsafe/_speedups.c,sha256=SglUjn40ti9YgQAO--OgkSyv9tXq9vvaHyVhQows4Ok,4353
markupsafe/_speedups.cp313-win_amd64.pyd,sha256=7MA12j0aUiSeNpFy-98h_pPSqgCpLeRacgp3I-j00Yo,13312
markupsafe/_speedups.pyi,sha256=LSDmXYOefH4HVpAXuL8sl7AttLw0oXh1njVoVZp2wqQ,42
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

View File

@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (75.2.0)
Root-Is-Purelib: false
Tag: cp313-cp313-win_amd64

View File

@@ -0,0 +1 @@
markupsafe

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,82 @@
Metadata-Version: 2.4
Name: click
Version: 8.2.1
Summary: Composable command line interface toolkit
Maintainer-email: Pallets <contact@palletsprojects.com>
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-Expression: BSD-3-Clause
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Typing :: Typed
License-File: LICENSE.txt
Requires-Dist: colorama; platform_system == 'Windows'
Project-URL: Changes, https://click.palletsprojects.com/page/changes/
Project-URL: Chat, https://discord.gg/pallets
Project-URL: Documentation, https://click.palletsprojects.com/
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Source, https://github.com/pallets/click/
# $ click_
Click is a Python package for creating beautiful command line interfaces
in a composable way with as little code as necessary. It's the "Command
Line Interface Creation Kit". It's highly configurable but comes with
sensible defaults out of the box.
It aims to make the process of writing command line tools quick and fun
while also preventing any frustration caused by the inability to
implement an intended CLI API.
Click in three points:
- Arbitrary nesting of commands
- Automatic help page generation
- Supports lazy loading of subcommands at runtime
## A Simple Example
```python
import click
@click.command()
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for _ in range(count):
click.echo(f"Hello, {name}!")
if __name__ == '__main__':
hello()
```
```
$ python hello.py --count=3
Your name: Click
Hello, Click!
Hello, Click!
Hello, Click!
```
## Donate
The Pallets organization develops and supports Click and other popular
packages. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, [please
donate today][].
[please donate today]: https://palletsprojects.com/donate
## Contributing
See our [detailed contributing documentation][contrib] for many ways to
contribute, including reporting issues, requesting features, asking or answering
questions, and making PRs.
[contrib]: https://palletsprojects.com/contributing/

View File

@@ -0,0 +1,38 @@
click-8.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
click-8.2.1.dist-info/METADATA,sha256=dI1MbhHTLoKD2tNCCGnx9rK2gok23HDNylFeLKdLSik,2471
click-8.2.1.dist-info/RECORD,,
click-8.2.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
click-8.2.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
click/__pycache__/__init__.cpython-313.pyc,,
click/__pycache__/_compat.cpython-313.pyc,,
click/__pycache__/_termui_impl.cpython-313.pyc,,
click/__pycache__/_textwrap.cpython-313.pyc,,
click/__pycache__/_winconsole.cpython-313.pyc,,
click/__pycache__/core.cpython-313.pyc,,
click/__pycache__/decorators.cpython-313.pyc,,
click/__pycache__/exceptions.cpython-313.pyc,,
click/__pycache__/formatting.cpython-313.pyc,,
click/__pycache__/globals.cpython-313.pyc,,
click/__pycache__/parser.cpython-313.pyc,,
click/__pycache__/shell_completion.cpython-313.pyc,,
click/__pycache__/termui.cpython-313.pyc,,
click/__pycache__/testing.cpython-313.pyc,,
click/__pycache__/types.cpython-313.pyc,,
click/__pycache__/utils.cpython-313.pyc,,
click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
click/_termui_impl.py,sha256=ASXhLi9IQIc0Js9KQSS-3-SLZcPet3VqysBf9WgbbpI,26712
click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
click/core.py,sha256=gUhpNS9cFBGdEXXdisGVG-eRvGf49RTyFagxulqwdFw,117343
click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
click/exceptions.py,sha256=1rdtXgHJ1b3OjGkN-UpXB9t_HCBihJvh_DtpmLmwn9s,9891
click/formatting.py,sha256=Bhqx4QXdKQ9W4WKknIwj5KPKFmtduGOuGq1yw_THLZ8,9726
click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
click/parser.py,sha256=nU1Ah2p11q29ul1vNdU9swPo_PUuKrxU6YXToi71q1c,18979
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
click/shell_completion.py,sha256=CQSGdjgun4ORbOZrXP0CVhEtPx4knsufOkRsDiK64cM,19857
click/termui.py,sha256=vAYrKC2a7f_NfEIhAThEVYfa__ib5XQbTSCGtJlABRA,30847
click/testing.py,sha256=2eLdAaCJCGToP5Tw-XN8JjrDb3wbJIfARxg3d0crW5M,18702
click/types.py,sha256=KBTRxN28cR1VZ5mb9iJX98MQSw_p9SGzljqfEI8z5Tw,38389
click/utils.py,sha256=b1Mm-usEDBHtEwcPltPIn3zSK4nw2KTp5GC7_oSTlLo,20245

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: flit 3.12.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,28 @@
Copyright 2014 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,28 @@
Copyright 2010 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,116 @@
Metadata-Version: 2.1
Name: Flask
Version: 3.0.0
Summary: A simple framework for building complex web applications.
Maintainer-email: Pallets <contact@palletsprojects.com>
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Requires-Dist: Werkzeug>=3.0.0
Requires-Dist: Jinja2>=3.1.2
Requires-Dist: itsdangerous>=2.1.2
Requires-Dist: click>=8.1.3
Requires-Dist: blinker>=1.6.2
Requires-Dist: importlib-metadata>=3.6.0; python_version < '3.10'
Requires-Dist: asgiref>=3.2 ; extra == "async"
Requires-Dist: python-dotenv ; extra == "dotenv"
Project-URL: Changes, https://flask.palletsprojects.com/changes/
Project-URL: Chat, https://discord.gg/pallets
Project-URL: Documentation, https://flask.palletsprojects.com/
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
Project-URL: Source Code, https://github.com/pallets/flask/
Provides-Extra: async
Provides-Extra: dotenv
Flask
=====
Flask is a lightweight `WSGI`_ web application framework. It is designed
to make getting started quick and easy, with the ability to scale up to
complex applications. It began as a simple wrapper around `Werkzeug`_
and `Jinja`_ and has become one of the most popular Python web
application frameworks.
Flask offers suggestions, but doesn't enforce any dependencies or
project layout. It is up to the developer to choose the tools and
libraries they want to use. There are many extensions provided by the
community that make adding new functionality easy.
.. _WSGI: https://wsgi.readthedocs.io/
.. _Werkzeug: https://werkzeug.palletsprojects.com/
.. _Jinja: https://jinja.palletsprojects.com/
Installing
----------
Install and update using `pip`_:
.. code-block:: text
$ pip install -U Flask
.. _pip: https://pip.pypa.io/en/stable/getting-started/
A Simple Example
----------------
.. code-block:: python
# save this as app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
.. code-block:: text
$ flask run
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Contributing
------------
For guidance on setting up a development environment and how to make a
contribution to Flask, see the `contributing guidelines`_.
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
Donate
------
The Pallets organization develops and supports Flask and the libraries
it uses. In order to grow the community of contributors and users, and
allow the maintainers to devote more time to the projects, `please
donate today`_.
.. _please donate today: https://palletsprojects.com/donate
Links
-----
- Documentation: https://flask.palletsprojects.com/
- Changes: https://flask.palletsprojects.com/changes/
- PyPI Releases: https://pypi.org/project/Flask/
- Source Code: https://github.com/pallets/flask/
- Issue Tracker: https://github.com/pallets/flask/issues/
- Chat: https://discord.gg/pallets

View File

@@ -0,0 +1,58 @@
../../Scripts/flask.exe,sha256=ND9f0z8XN9fqR_S6jQEm-9esibL5LMMT-NWKLivt0pI,108381
flask-3.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
flask-3.0.0.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
flask-3.0.0.dist-info/METADATA,sha256=02XP69VTiwn5blcRgHcyuSQ2cLTuJFV8FXw2x4QnxKo,3588
flask-3.0.0.dist-info/RECORD,,
flask-3.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask-3.0.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
flask-3.0.0.dist-info/entry_points.txt,sha256=bBP7hTOS5fz9zLtC7sPofBZAlMkEvBxu7KqS6l5lvc4,40
flask/__init__.py,sha256=6xMqdVA0FIQ2U1KVaGX3lzNCdXPzoHPaa0hvQCNcfSk,2625
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
flask/__pycache__/__init__.cpython-313.pyc,,
flask/__pycache__/__main__.cpython-313.pyc,,
flask/__pycache__/app.cpython-313.pyc,,
flask/__pycache__/blueprints.cpython-313.pyc,,
flask/__pycache__/cli.cpython-313.pyc,,
flask/__pycache__/config.cpython-313.pyc,,
flask/__pycache__/ctx.cpython-313.pyc,,
flask/__pycache__/debughelpers.cpython-313.pyc,,
flask/__pycache__/globals.cpython-313.pyc,,
flask/__pycache__/helpers.cpython-313.pyc,,
flask/__pycache__/logging.cpython-313.pyc,,
flask/__pycache__/sessions.cpython-313.pyc,,
flask/__pycache__/signals.cpython-313.pyc,,
flask/__pycache__/templating.cpython-313.pyc,,
flask/__pycache__/testing.cpython-313.pyc,,
flask/__pycache__/typing.cpython-313.pyc,,
flask/__pycache__/views.cpython-313.pyc,,
flask/__pycache__/wrappers.cpython-313.pyc,,
flask/app.py,sha256=voUkc9xk9B039AhVrU21GDpsQ6wqrr-NobqLx8fURfQ,59201
flask/blueprints.py,sha256=zO8bLO9Xy1aVD92bDmzihutjVEXf8xdDaVfiy7c--Ck,3129
flask/cli.py,sha256=PDwZCfPagi5GUzb-D6dEN7y20gWiVAg3ejRnxBKNHPA,33821
flask/config.py,sha256=YZSZ-xpFj1iW1B1Kj1iDhpc5s7pHncloiRLqXhsU7Hs,12856
flask/ctx.py,sha256=x2kGzUXtPzVyi2YSKrU_PV1AvtxTmh2iRdriJRTSPGM,14841
flask/debughelpers.py,sha256=WKzD2FNTSimNSwCJVLr9_fFo1f2VlTWB5EZ6lmR5bwE,5548
flask/globals.py,sha256=XdQZmStBmPIs8t93tjx6pO7Bm3gobAaONWkFcUHaGas,1713
flask/helpers.py,sha256=ynEoMB7fdF5Y1P-ngxMjZDZWfrJ4St-9OGZZsTcUwx8,22992
flask/json/__init__.py,sha256=pdtpoK2b0b1u7Sxbx3feM7VWhsI20l1yGAvbYWxaxvc,5572
flask/json/__pycache__/__init__.cpython-313.pyc,,
flask/json/__pycache__/provider.cpython-313.pyc,,
flask/json/__pycache__/tag.cpython-313.pyc,,
flask/json/provider.py,sha256=VBKSK75t3OsTvZ3N10B3Fsu7-NdpfrGYcl41goQJ3q8,7640
flask/json/tag.py,sha256=ihb7QWrNEr0YC3KD4TolZbftgSPCuLk7FAvK49huYC0,8871
flask/logging.py,sha256=VcdJgW4Axm5l_-7vXLQjRTL0eckaMks7Ya_HaoDm0wg,2330
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
flask/sansio/README.md,sha256=-0X1tECnilmz1cogx-YhNw5d7guK7GKrq_DEV2OzlU0,228
flask/sansio/__pycache__/app.cpython-313.pyc,,
flask/sansio/__pycache__/blueprints.cpython-313.pyc,,
flask/sansio/__pycache__/scaffold.cpython-313.pyc,,
flask/sansio/app.py,sha256=nZWCFMOW8qK95Ck9UvDzxvswQr-coLJhIFaa_OVobCc,37977
flask/sansio/blueprints.py,sha256=caskVI1Zf3mM5msevK5-tWy3VqX_A8mlB0KGNyRx5_0,24319
flask/sansio/scaffold.py,sha256=-Cus0cVS4PmLof4qLvfjSQzk4AKsLqPR6LBpv6ALw3Y,30580
flask/sessions.py,sha256=rFH2QKXG24dEazkKGxAHqUpAUh_30hDHrddhVYgAcY0,14169
flask/signals.py,sha256=V7lMUww7CqgJ2ThUBn1PiatZtQanOyt7OZpu2GZI-34,750
flask/templating.py,sha256=EtL8CE5z2aefdR1I-TWYVNg0cSuXBqz_lvOGKeggktk,7538
flask/testing.py,sha256=h7AinggrMgGzKlDN66VfB0JjWW4Z1U_OD6FyjqBNiYM,10017
flask/typing.py,sha256=2pGlhSaZqJVJOoh-QdH-20QVzl2r-zLXyP8otXfCCs4,3156
flask/views.py,sha256=V5hOGZLx0Bn99QGcM6mh5x_uM-MypVT0-RysEFU84jc,6789
flask/wrappers.py,sha256=PhMp3teK3SnEmIdog59cO_DHiZ9Btn0qI1EifrTdwP8,5709

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: flit 3.9.0
Root-Is-Purelib: true
Tag: py3-none-any

View File

@@ -0,0 +1,3 @@
[console_scripts]
flask=flask.cli:main

View File

@@ -0,0 +1 @@
pip

View File

@@ -0,0 +1,28 @@
Copyright 2011 Pallets
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,60 @@
Metadata-Version: 2.1
Name: itsdangerous
Version: 2.2.0
Summary: Safely pass data to untrusted environments and back.
Maintainer-email: Pallets <contact@palletsprojects.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Typing :: Typed
Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/
Project-URL: Chat, https://discord.gg/pallets
Project-URL: Documentation, https://itsdangerous.palletsprojects.com/
Project-URL: Donate, https://palletsprojects.com/donate
Project-URL: Source, https://github.com/pallets/itsdangerous/
# ItsDangerous
... so better sign this
Various helpers to pass data to untrusted environments and to get it
back safe and sound. Data is cryptographically signed to ensure that a
token has not been tampered with.
It's possible to customize how data is serialized. Data is compressed as
needed. A timestamp can be added and verified automatically while
loading a token.
## A Simple Example
Here's how you could generate a token for transmitting a user's id and
name between web requests.
```python
from itsdangerous import URLSafeSerializer
auth_s = URLSafeSerializer("secret key", "auth")
token = auth_s.dumps({"id": 5, "name": "itsdangerous"})
print(token)
# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg
data = auth_s.loads(token)
print(data["name"])
# itsdangerous
```
## Donate
The Pallets organization develops and supports ItsDangerous and other
popular packages. In order to grow the community of contributors and
users, and allow the maintainers to devote more time to the projects,
[please donate today][].
[please donate today]: https://palletsprojects.com/donate

View File

@@ -0,0 +1,22 @@
itsdangerous-2.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
itsdangerous-2.2.0.dist-info/LICENSE.txt,sha256=Y68JiRtr6K0aQlLtQ68PTvun_JSOIoNnvtfzxa4LCdc,1475
itsdangerous-2.2.0.dist-info/METADATA,sha256=0rk0-1ZwihuU5DnwJVwPWoEI4yWOyCexih3JyZHblhE,1924
itsdangerous-2.2.0.dist-info/RECORD,,
itsdangerous-2.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
itsdangerous/__init__.py,sha256=4SK75sCe29xbRgQE1ZQtMHnKUuZYAf3bSpZOrff1IAY,1427
itsdangerous/__pycache__/__init__.cpython-313.pyc,,
itsdangerous/__pycache__/_json.cpython-313.pyc,,
itsdangerous/__pycache__/encoding.cpython-313.pyc,,
itsdangerous/__pycache__/exc.cpython-313.pyc,,
itsdangerous/__pycache__/serializer.cpython-313.pyc,,
itsdangerous/__pycache__/signer.cpython-313.pyc,,
itsdangerous/__pycache__/timed.cpython-313.pyc,,
itsdangerous/__pycache__/url_safe.cpython-313.pyc,,
itsdangerous/_json.py,sha256=wPQGmge2yZ9328EHKF6gadGeyGYCJQKxtU-iLKE6UnA,473
itsdangerous/encoding.py,sha256=wwTz5q_3zLcaAdunk6_vSoStwGqYWe307Zl_U87aRFM,1409
itsdangerous/exc.py,sha256=Rr3exo0MRFEcPZltwecyK16VV1bE2K9_F1-d-ljcUn4,3201
itsdangerous/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
itsdangerous/serializer.py,sha256=PmdwADLqkSyQLZ0jOKAgDsAW4k_H0TlA71Ei3z0C5aI,15601
itsdangerous/signer.py,sha256=YO0CV7NBvHA6j549REHJFUjUojw2pHqwcUpQnU7yNYQ,9647
itsdangerous/timed.py,sha256=6RvDMqNumGMxf0-HlpaZdN9PUQQmRvrQGplKhxuivUs,8083
itsdangerous/url_safe.py,sha256=az4e5fXi_vs-YbWj8YZwn4wiVKfeD--GEKRT5Ueu4P4,2505

View File

@@ -0,0 +1,4 @@
Wheel-Version: 1.0
Generator: flit 3.9.0
Root-Is-Purelib: true
Tag: py3-none-any

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,35 @@
# Plugins pour PC Monitor
from .base import BasePlugin
from .librehardwaremonitor import LibreHardwareMonitorPlugin
from .hwinfo import HWiNFOPlugin
# Registry des plugins disponibles
PLUGINS = {
'librehardwaremonitor': LibreHardwareMonitorPlugin,
'hwinfo': HWiNFOPlugin
}
def get_plugin(name: str, config: dict) -> BasePlugin:
"""Retourne une instance du plugin demandé"""
if name not in PLUGINS:
raise ValueError(f"Plugin inconnu: {name}")
return PLUGINS[name](config)
def get_available_plugins() -> list:
"""Retourne la liste des plugins disponibles"""
return [
{
'id': 'librehardwaremonitor',
'name': 'LibreHardwareMonitor',
'description': 'Open source, API REST intégrée',
'default_port': 8085,
'website': 'https://github.com/LibreHardwareMonitor/LibreHardwareMonitor'
},
{
'id': 'hwinfo',
'name': 'HWiNFO + RemoteHWInfo',
'description': 'HWiNFO avec RemoteHWInfo (github.com/Demion/remotehwinfo)',
'default_port': 60000,
'website': 'https://github.com/Demion/remotehwinfo'
}
]

View File

@@ -0,0 +1,151 @@
"""
Classe de base pour tous les plugins de monitoring
"""
from abc import ABC, abstractmethod
from typing import Optional, List, Dict, Any
class BasePlugin(ABC):
"""Classe abstraite définissant l'interface des plugins"""
def __init__(self, config: dict):
"""
Initialise le plugin avec sa configuration
Args:
config: Configuration du plugin (host, port, etc.)
"""
self.host = config.get('host', '127.0.0.1')
self.port = config.get('port', self.get_default_port())
self.config = config
@abstractmethod
def get_id(self) -> str:
"""Retourne l'identifiant unique du plugin"""
pass
@abstractmethod
def get_name(self) -> str:
"""Retourne le nom affichable du plugin"""
pass
@abstractmethod
def get_default_port(self) -> int:
"""Retourne le port par défaut"""
pass
@abstractmethod
def test_connection(self) -> Dict[str, Any]:
"""
Teste la connexion au logiciel de monitoring
Returns:
dict avec 'success' (bool), 'message' (str), et optionnellement 'version'
"""
pass
@abstractmethod
def get_data(self) -> Optional[dict]:
"""
Récupère les données brutes du logiciel de monitoring
Returns:
Données brutes ou None si erreur
"""
pass
@abstractmethod
def get_hierarchy(self) -> List[dict]:
"""
Récupère la hiérarchie des capteurs (pour l'admin)
Returns:
Liste de hardware avec leurs capteurs
"""
pass
@abstractmethod
def parse_sensors(self, data: dict) -> List[dict]:
"""
Parse les données brutes en liste standardisée de capteurs
Args:
data: Données brutes du logiciel
Returns:
Liste de capteurs au format standardisé:
[
{
'id': str, # Identifiant unique
'name': str, # Nom du capteur
'value': str, # Valeur formatée (ex: "45 °C")
'raw_value': float, # Valeur numérique
'type': str, # Type (temperature, load, voltage, etc.)
'unit': str, # Unité (°C, %, MHz, etc.)
'hardware': str, # Nom du hardware parent
'hardware_type': str # Type de hardware (CPU, GPU, etc.)
},
...
]
"""
pass
def get_base_url(self) -> str:
"""Retourne l'URL de base pour les requêtes"""
return f"http://{self.host}:{self.port}"
def get_sensor_type(self, unit: str, name: str = '') -> str:
"""
Détermine le type de capteur à partir de l'unité et du nom
Args:
unit: Unité du capteur
name: Nom du capteur (pour affiner la détection)
Returns:
Type standardisé du capteur
"""
unit_lower = unit.lower() if unit else ''
name_lower = name.lower() if name else ''
# Détection par unité
if '°c' in unit_lower or '°f' in unit_lower:
return 'temperature'
elif '%' in unit_lower:
if 'load' in name_lower or 'usage' in name_lower:
return 'load'
return 'percentage'
elif 'mhz' in unit_lower or 'ghz' in unit_lower:
return 'frequency'
elif 'rpm' in unit_lower:
return 'fan'
elif 'w' in unit_lower and 'wh' not in unit_lower:
return 'power'
elif 'v' in unit_lower and 'mv' not in unit_lower:
return 'voltage'
elif 'mv' in unit_lower:
return 'voltage'
elif 'mb' in unit_lower or 'gb' in unit_lower or 'kb' in unit_lower:
return 'data'
elif 'mb/s' in unit_lower or 'kb/s' in unit_lower or 'gb/s' in unit_lower:
return 'throughput'
elif 'a' in unit_lower:
return 'current'
elif 'wh' in unit_lower:
return 'energy'
# Détection par nom si unité non reconnue
if 'temp' in name_lower or 'temperature' in name_lower:
return 'temperature'
elif 'fan' in name_lower:
return 'fan'
elif 'clock' in name_lower or 'freq' in name_lower:
return 'frequency'
elif 'load' in name_lower or 'usage' in name_lower:
return 'load'
elif 'power' in name_lower:
return 'power'
elif 'voltage' in name_lower or 'vcore' in name_lower:
return 'voltage'
return 'generic'

View File

@@ -0,0 +1,274 @@
"""
Plugin pour HWiNFO via RemoteHWInfo (Demion)
https://github.com/Demion/remotehwinfo
Format JSON RemoteHWInfo:
{
"hwinfo": {
"sensors": [
{"entryIndex": 0, "sensorNameUser": "System", ...},
{"entryIndex": 1, "sensorNameUser": "CPU [#0]: Intel Core i7", ...}
],
"readings": [
{"sensorIndex": 0, "labelUser": "Memory Load", "value": 45.5, "unit": "%", ...},
{"sensorIndex": 1, "labelUser": "CPU Temp", "value": 65.0, "unit": "°C", ...}
]
}
}
"""
import requests
from typing import Dict, List, Optional, Any
from .base import BasePlugin
class HWiNFOPlugin(BasePlugin):
"""Plugin pour HWiNFO via RemoteHWInfo"""
def __init__(self, config: dict):
super().__init__(config)
self.history = {}
self.max_history = 120
def get_id(self) -> str:
return 'hwinfo'
def get_name(self) -> str:
return 'HWiNFO'
def get_default_port(self) -> int:
return 60000
def get_base_url(self) -> str:
"""Retourne l'URL de l'API RemoteHWInfo"""
return f"http://{self.host}:{self.port}/json.json"
def test_connection(self) -> Dict[str, Any]:
"""Teste la connexion à RemoteHWInfo"""
try:
url = self.get_base_url()
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
# Vérifier le format RemoteHWInfo
if 'hwinfo' not in data:
return {
'success': False,
'message': 'Format invalide. Vérifiez que RemoteHWInfo est lancé.'
}
hwinfo = data['hwinfo']
sensor_count = len(hwinfo.get('sensors', []))
reading_count = len(hwinfo.get('readings', []))
return {
'success': True,
'message': f'Connecté - {sensor_count} capteurs, {reading_count} lectures',
'version': 'RemoteHWInfo',
'sensor_count': reading_count
}
except requests.exceptions.ConnectionError:
return {
'success': False,
'message': f'Impossible de se connecter à {self.get_base_url()}. Vérifiez que RemoteHWInfo est lancé.'
}
except requests.exceptions.Timeout:
return {
'success': False,
'message': 'Timeout - Le serveur ne répond pas'
}
except Exception as e:
return {
'success': False,
'message': f'Erreur: {str(e)}'
}
def get_data(self) -> Optional[Dict]:
"""Récupère les données JSON depuis RemoteHWInfo"""
try:
url = self.get_base_url()
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
if 'hwinfo' in data:
return data['hwinfo']
return None
except Exception as e:
print(f"Erreur HWiNFO get_data: {e}")
return None
def get_hierarchy(self) -> List[dict]:
"""Récupère la hiérarchie des capteurs pour l'admin"""
data = self.get_data()
if not data:
return []
return self._build_hierarchy(data)
def _build_hierarchy(self, data: Dict) -> List[dict]:
"""Construit la hiérarchie des capteurs"""
sensors = data.get('sensors', [])
readings = data.get('readings', [])
# Créer un dictionnaire des sensors par index
sensor_map = {}
for sensor in sensors:
idx = sensor.get('entryIndex', 0)
sensor_map[idx] = {
'name': sensor.get('sensorNameUser', sensor.get('sensorNameOriginal', 'Unknown')),
'value': '',
'id': '',
'level': 0,
'type': 'group',
'children': []
}
# Ajouter les readings aux sensors correspondants
for reading in readings:
sensor_idx = reading.get('sensorIndex', 0)
if sensor_idx not in sensor_map:
continue
label = reading.get('labelUser', reading.get('labelOriginal', 'Unknown'))
value = reading.get('value', 0)
unit = reading.get('unit', '')
# Générer un ID unique
sensor_id = self._generate_sensor_id(sensor_idx, reading.get('readingId', 0), label)
# Formater la valeur
if isinstance(value, float):
if value == int(value):
formatted_value = f"{int(value)} {unit}".strip()
else:
formatted_value = f"{value:.2f} {unit}".strip()
else:
formatted_value = f"{value} {unit}".strip()
reading_entry = {
'name': label,
'value': formatted_value,
'id': sensor_id,
'level': 1,
'type': self.get_sensor_type(unit, label),
'children': []
}
sensor_map[sensor_idx]['children'].append(reading_entry)
# Retourner seulement les sensors qui ont des readings
return [s for s in sensor_map.values() if s['children']]
def parse_sensors(self, data: Dict) -> List[Dict]:
"""Parse les données et retourne une liste plate de capteurs"""
sensors_list = []
if not data:
return sensors_list
sensors = data.get('sensors', [])
readings = data.get('readings', [])
# Créer un dictionnaire des sensors par index
sensor_names = {}
for sensor in sensors:
idx = sensor.get('entryIndex', 0)
sensor_names[idx] = sensor.get('sensorNameUser', sensor.get('sensorNameOriginal', 'Unknown'))
# Parser chaque reading
for reading in readings:
sensor_idx = reading.get('sensorIndex', 0)
hardware_name = sensor_names.get(sensor_idx, 'Unknown')
label = reading.get('labelUser', reading.get('labelOriginal', 'Unknown'))
value = reading.get('value', 0)
unit = reading.get('unit', '')
# Générer un ID unique
sensor_id = self._generate_sensor_id(sensor_idx, reading.get('readingId', 0), label)
# Formater la valeur
if isinstance(value, float):
if value == int(value):
formatted_value = f"{int(value)} {unit}".strip()
else:
formatted_value = f"{value:.2f} {unit}".strip()
else:
formatted_value = f"{value} {unit}".strip()
sensor_type = self.get_sensor_type(unit, label)
hardware_type = self._guess_hardware_type(hardware_name)
sensor = {
'id': sensor_id,
'name': label,
'value': formatted_value,
'raw_value': float(value) if value else 0.0,
'type': sensor_type,
'unit': unit,
'hardware': hardware_name,
'hardware_type': hardware_type,
'category': hardware_name,
'path': f"{hardware_name}/{label}",
}
sensors_list.append(sensor)
return sensors_list
def _generate_sensor_id(self, sensor_idx: int, reading_id: int, label: str) -> str:
"""Génère un ID unique pour un capteur"""
# Nettoyer le label pour l'ID
clean_label = label.replace(' ', '_').replace('[', '').replace(']', '')
clean_label = clean_label.replace('#', '').replace(':', '_').replace('/', '_')
return f"/hwinfo/{sensor_idx}_{reading_id}_{clean_label}"
def _guess_hardware_type(self, sensor_name: str) -> str:
"""Devine le type de hardware basé sur le nom du sensor"""
name_lower = sensor_name.lower()
if 'cpu' in name_lower or 'processor' in name_lower:
return 'CPU'
elif 'gpu' in name_lower or 'graphics' in name_lower or 'radeon' in name_lower or 'geforce' in name_lower:
return 'GPU'
elif 'memory' in name_lower or 'ram' in name_lower or 'dimm' in name_lower:
return 'RAM'
elif 'drive' in name_lower or 'disk' in name_lower or 'ssd' in name_lower or 'hdd' in name_lower or 's.m.a.r.t' in name_lower:
return 'Storage'
elif 'network' in name_lower or 'ethernet' in name_lower or 'wifi' in name_lower or 'réseau' in name_lower:
return 'Network'
elif 'system' in name_lower or 'système' in name_lower:
return 'System'
else:
return 'Other'
# === Gestion de l'historique ===
def update_history(self, sensors: List[Dict]):
"""Met à jour l'historique des capteurs pour les graphiques"""
for sensor in sensors:
sensor_id = sensor.get("id", "")
if not sensor_id:
continue
raw_value = sensor.get("raw_value", 0.0)
if sensor_id not in self.history:
self.history[sensor_id] = []
self.history[sensor_id].append(raw_value)
if len(self.history[sensor_id]) > self.max_history:
self.history[sensor_id] = self.history[sensor_id][-self.max_history:]
def get_history(self, sensor_id: str, count: int = 60) -> List[float]:
"""Récupère l'historique d'un capteur"""
if sensor_id not in self.history:
return []
return self.history[sensor_id][-count:]

View File

@@ -0,0 +1,266 @@
"""
Plugin pour LibreHardwareMonitor
Utilise l'API REST intégrée de LHM (port 8085 par défaut)
"""
import requests
from typing import Dict, List, Optional, Any
from .base import BasePlugin
class LibreHardwareMonitorPlugin(BasePlugin):
"""Plugin pour LibreHardwareMonitor"""
def __init__(self, config: dict):
super().__init__(config)
self.history = {}
self.max_history = 120 # 2 minutes à 1 sample/sec
def get_id(self) -> str:
return 'librehardwaremonitor'
def get_name(self) -> str:
return 'LibreHardwareMonitor'
def get_default_port(self) -> int:
return 8085
def test_connection(self) -> Dict[str, Any]:
"""Teste la connexion à LibreHardwareMonitor"""
try:
url = f"{self.get_base_url()}/data.json"
response = requests.get(url, timeout=5)
response.raise_for_status()
data = response.json()
# Extraire la version si disponible
version = "Inconnue"
if data and "Text" in data:
version = data.get("Text", "LibreHardwareMonitor")
# Compter les capteurs
sensors = self.parse_sensors(data)
return {
'success': True,
'message': f'Connecté - {len(sensors)} capteurs détectés',
'version': version,
'sensor_count': len(sensors)
}
except requests.exceptions.ConnectionError:
return {
'success': False,
'message': f'Impossible de se connecter à {self.get_base_url()}. Vérifiez que LibreHardwareMonitor est lancé avec le serveur web activé.'
}
except requests.exceptions.Timeout:
return {
'success': False,
'message': 'Timeout - Le serveur ne répond pas'
}
except Exception as e:
return {
'success': False,
'message': f'Erreur: {str(e)}'
}
def get_data(self) -> Optional[Dict]:
"""Récupère les données JSON depuis LHM Remote Server"""
try:
url = f"{self.get_base_url()}/data.json"
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"Erreur LHM get_data: {e}")
return None
def get_hierarchy(self) -> List[dict]:
"""Récupère la hiérarchie des capteurs pour l'admin"""
data = self.get_data()
if not data:
return []
return self._build_hierarchy(data)
def _build_hierarchy(self, data: Dict) -> List[dict]:
"""Construit la hiérarchie des capteurs"""
if not data or "Children" not in data:
return []
def build_tree(node, level=0):
node_text = node.get("Text", "")
node_value = node.get("Value", "")
sensor_id = node.get("SensorId", "")
result = {
"name": node_text,
"value": node_value,
"id": sensor_id,
"level": level,
"type": self._guess_sensor_type(node_text, node_value) if node_value else "group",
"children": []
}
if "Children" in node and node["Children"]:
for child in node["Children"]:
result["children"].append(build_tree(child, level + 1))
return result
tree = []
for child in data.get("Children", []):
tree.append(build_tree(child))
return tree
def parse_sensors(self, data: Dict) -> List[Dict]:
"""Parse les données et retourne une liste plate de capteurs"""
sensors = []
if not data or "Children" not in data:
return sensors
def extract_sensors(node, parent_name="", hardware_name="", hardware_type=""):
node_text = node.get("Text", "")
current_path = f"{parent_name}/{node_text}" if parent_name else node_text
# Détecter le type de hardware au niveau 1
if not hardware_name and "Children" in node:
hw_type = self._guess_hardware_type(node_text)
hardware_name = node_text
hardware_type = hw_type
# Si le nœud a une valeur, c'est un capteur
if "Value" in node and node["Value"]:
sensor_id = node.get("SensorId", "")
if not sensor_id:
sensor_id = f"{current_path}".replace(" ", "-").lower()
value_str = node.get("Value", "")
raw_value = self._extract_numeric_value(value_str)
unit = self._extract_unit(value_str)
sensor_type = self._guess_sensor_type(node_text, value_str)
sensor = {
"id": sensor_id,
"name": node_text,
"value": value_str,
"raw_value": raw_value,
"type": sensor_type,
"unit": unit,
"hardware": hardware_name,
"hardware_type": hardware_type,
"category": parent_name.split("/")[0] if "/" in parent_name else parent_name,
"path": current_path,
"min": node.get("Min", ""),
"max": node.get("Max", ""),
}
sensors.append(sensor)
# Parcourir les enfants récursivement
if "Children" in node:
for child in node["Children"]:
extract_sensors(child, current_path, hardware_name, hardware_type)
for child in data.get("Children", []):
extract_sensors(child)
return sensors
def _guess_hardware_type(self, name: str) -> str:
"""Devine le type de hardware basé sur le nom"""
name_lower = name.lower()
if 'cpu' in name_lower or 'processor' in name_lower:
return 'CPU'
elif 'gpu' in name_lower or 'graphics' in name_lower or 'nvidia' in name_lower or 'amd' in name_lower or 'radeon' in name_lower or 'geforce' in name_lower:
return 'GPU'
elif 'memory' in name_lower or 'ram' in name_lower:
return 'RAM'
elif 'motherboard' in name_lower or 'mainboard' in name_lower:
return 'Motherboard'
elif 'storage' in name_lower or 'disk' in name_lower or 'ssd' in name_lower or 'hdd' in name_lower or 'nvme' in name_lower:
return 'Storage'
elif 'network' in name_lower or 'ethernet' in name_lower or 'wifi' in name_lower:
return 'Network'
elif 'battery' in name_lower:
return 'Battery'
else:
return 'Other'
def _guess_sensor_type(self, name: str, value: str) -> str:
"""Devine le type de capteur basé sur le nom et la valeur"""
name_lower = name.lower()
value_lower = value.lower()
if "°c" in value_lower or "temperature" in name_lower or "temp" in name_lower:
return "temperature"
elif "%" in value or "load" in name_lower or "usage" in name_lower:
return "percentage"
elif "rpm" in value_lower or "fan" in name_lower:
return "fan"
elif "mhz" in value_lower or "ghz" in value_lower or "clock" in name_lower:
return "frequency"
elif "w" in value_lower and "wh" not in value_lower:
return "power"
elif "v" in value_lower:
return "voltage"
elif "gb" in value_lower or "mb" in value_lower or "memory" in name_lower:
return "memory"
else:
return "generic"
def _extract_numeric_value(self, value_str: str) -> float:
"""Extrait la valeur numérique d'une chaîne"""
try:
# Enlever tous les caractères non numériques sauf le point et la virgule
clean = value_str.replace("°C", "").replace("%", "").replace("V", "").replace("W", "")
clean = clean.replace("MHz", "").replace("GHz", "").replace("GB", "").replace("MB", "")
clean = clean.replace("RPM", "").replace(" ", "").replace(",", ".").strip()
if clean:
return float(clean)
except (ValueError, AttributeError):
pass
return 0.0
def _extract_unit(self, value_str: str) -> str:
"""Extrait l'unité d'une valeur"""
value_str = value_str.strip()
units = ['°C', '°F', '%', 'MHz', 'GHz', 'GB', 'MB', 'KB', 'RPM', 'W', 'V', 'A', 'GB/s', 'MB/s']
for unit in units:
if unit in value_str:
return unit
return ''
# === Gestion de l'historique ===
def update_history(self, sensors: List[Dict]):
"""Met à jour l'historique des capteurs pour les graphiques"""
for sensor in sensors:
sensor_id = sensor.get("id", "")
if not sensor_id:
continue
raw_value = sensor.get("raw_value")
if raw_value is None:
raw_value = self._extract_numeric_value(sensor.get("value", ""))
if raw_value == 0.0 and "0" not in sensor.get("value", ""):
continue
if sensor_id not in self.history:
self.history[sensor_id] = []
self.history[sensor_id].append(raw_value)
if len(self.history[sensor_id]) > self.max_history:
self.history[sensor_id] = self.history[sensor_id][-self.max_history:]
def get_history(self, sensor_id: str, count: int = 60) -> List[float]:
"""Récupère l'historique d'un capteur"""
if sensor_id not in self.history:
return []
return self.history[sensor_id][-count:]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More