PyObservability 0.0.0a1__py3-none-any.whl → 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyobservability/__init__.py +68 -0
- pyobservability/config/enums.py +6 -0
- pyobservability/config/settings.py +125 -0
- pyobservability/main.py +64 -12
- pyobservability/monitor.py +10 -51
- pyobservability/version.py +1 -1
- pyobservability-0.0.1.dist-info/METADATA +163 -0
- pyobservability-0.0.1.dist-info/RECORD +15 -0
- pyobservability-0.0.1.dist-info/entry_points.txt +2 -0
- pyobservability-0.0.0a1.dist-info/METADATA +0 -56
- pyobservability-0.0.0a1.dist-info/RECORD +0 -12
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.1.dist-info}/WHEEL +0 -0
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.1.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.1.dist-info}/top_level.txt +0 -0
pyobservability/__init__.py
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Module for packaging."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from pyobservability.main import start # noqa: F401
|
|
6
|
+
from pyobservability.version import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _cli() -> None:
|
|
10
|
+
"""Starter function to invoke the observability UI via CLI commands.
|
|
11
|
+
|
|
12
|
+
**Flags**
|
|
13
|
+
- ``--version | -V``: Prints the version.
|
|
14
|
+
- ``--help | -H``: Prints the help section.
|
|
15
|
+
- ``--env | -E <path>``: Filepath to load environment variables.
|
|
16
|
+
|
|
17
|
+
**Commands**
|
|
18
|
+
``start``: Initiates the PyObservability as a regular script.
|
|
19
|
+
"""
|
|
20
|
+
assert sys.argv[0].endswith("pyobservability"), "Invalid commandline trigger!!"
|
|
21
|
+
options = {
|
|
22
|
+
"--version | -V": "Prints the version.",
|
|
23
|
+
"--help | -H": "Prints the help section.",
|
|
24
|
+
"--env | -E <path>": "Filepath to load environment variables.",
|
|
25
|
+
"start": "Initiates the PyObservability as a regular script.",
|
|
26
|
+
}
|
|
27
|
+
# weird way to increase spacing to keep all values monotonic
|
|
28
|
+
_longest_key = len(max(options.keys()))
|
|
29
|
+
_pretext = "\n\t* "
|
|
30
|
+
choices = _pretext + _pretext.join(
|
|
31
|
+
f"{k} {'·' * (_longest_key - len(k) + 8)}→ {v}".expandtabs() for k, v in options.items()
|
|
32
|
+
)
|
|
33
|
+
args = [arg.lower() for arg in sys.argv[1:]]
|
|
34
|
+
try:
|
|
35
|
+
assert len(args) > 1
|
|
36
|
+
except (IndexError, AttributeError, AssertionError):
|
|
37
|
+
print(f"Cannot proceed without a valid arbitrary command. Please choose from {choices}")
|
|
38
|
+
exit(1)
|
|
39
|
+
env_file = None
|
|
40
|
+
if any(arg in args for arg in ["version", "--version", "-v"]):
|
|
41
|
+
print(f"PyObservability: {__version__}")
|
|
42
|
+
exit(0)
|
|
43
|
+
elif any(arg in args for arg in ["help", "--help", "-h"]):
|
|
44
|
+
print(f"Usage: pyobservability [arbitrary-command]\nOptions (and corresponding behavior):{choices}")
|
|
45
|
+
exit(0)
|
|
46
|
+
elif any(arg in args for arg in ["env", "--env", "E", "-e"]):
|
|
47
|
+
extra_index = next(
|
|
48
|
+
(index for index, arg in enumerate(args) if arg in ["env", "--env", "E", "-e"]),
|
|
49
|
+
None,
|
|
50
|
+
)
|
|
51
|
+
try:
|
|
52
|
+
env_file = sys.argv[extra_index + 2]
|
|
53
|
+
except (IndexError, TypeError):
|
|
54
|
+
print("Cannot proceed without a valid extra environment file path.")
|
|
55
|
+
exit(1)
|
|
56
|
+
elif any(arg in args for arg in ("start",)):
|
|
57
|
+
pass
|
|
58
|
+
else:
|
|
59
|
+
print(f"Unknown Option: {sys.argv[1]}\nArbitrary commands must be one of {choices}")
|
|
60
|
+
exit(1)
|
|
61
|
+
if any(arg in args for arg in ("start",)):
|
|
62
|
+
start(env_file=env_file)
|
|
63
|
+
else:
|
|
64
|
+
print(
|
|
65
|
+
"Insufficient Arguments:\n\tNo command received to initiate the PyObservability. "
|
|
66
|
+
f"Please choose from {choices}"
|
|
67
|
+
)
|
|
68
|
+
exit(1)
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import socket
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pydantic import BaseModel, Field, HttpUrl, PositiveInt
|
|
9
|
+
from pydantic.aliases import AliasChoices
|
|
10
|
+
from pydantic_settings import BaseSettings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PydanticEnvConfig(BaseSettings):
|
|
14
|
+
"""Pydantic BaseSettings with custom order for loading environment variables.
|
|
15
|
+
|
|
16
|
+
>>> PydanticEnvConfig
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def settings_customise_sources(
|
|
22
|
+
cls,
|
|
23
|
+
settings_cls,
|
|
24
|
+
init_settings,
|
|
25
|
+
env_settings,
|
|
26
|
+
dotenv_settings,
|
|
27
|
+
file_secret_settings,
|
|
28
|
+
):
|
|
29
|
+
"""Order: dotenv, env, init, secrets files."""
|
|
30
|
+
return dotenv_settings, env_settings, init_settings, file_secret_settings
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class MonitorTarget(BaseModel):
|
|
34
|
+
name: str
|
|
35
|
+
base_url: HttpUrl
|
|
36
|
+
apikey: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def alias_choices(variable: str) -> AliasChoices:
|
|
40
|
+
"""Custom alias choices for environment variables for GitHub.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
variable: Variable name.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
AliasChoices:
|
|
47
|
+
Returns the alias choices for the variable.
|
|
48
|
+
"""
|
|
49
|
+
return AliasChoices(variable, f"MONITOR_{variable}")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class EnvConfig(PydanticEnvConfig):
|
|
53
|
+
"""Configuration settings for the server.
|
|
54
|
+
|
|
55
|
+
>>> EnvConfig
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
host: str = Field(socket.gethostbyname("localhost") or "0.0.0.0", validation_alias=alias_choices("HOST"))
|
|
60
|
+
port: PositiveInt = Field(8080, validation_alias=alias_choices("PORT"))
|
|
61
|
+
|
|
62
|
+
targets: List[MonitorTarget] = Field(..., validation_alias=alias_choices("TARGETS"))
|
|
63
|
+
interval: PositiveInt = Field(3, validation_alias=alias_choices("INTERVAL"))
|
|
64
|
+
|
|
65
|
+
username: str | None = Field(None, validation_alias=alias_choices("USERNAME"))
|
|
66
|
+
password: str | None = Field(None, validation_alias=alias_choices("PASSWORD"))
|
|
67
|
+
|
|
68
|
+
class Config:
|
|
69
|
+
"""Environment variables configuration."""
|
|
70
|
+
|
|
71
|
+
env_prefix = ""
|
|
72
|
+
extra = "forbid"
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_env_file(cls, filename: pathlib.Path) -> "EnvConfig":
|
|
76
|
+
"""Create an instance of EnvConfig from environment file.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
filename: Name of the env file.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
EnvConfig:
|
|
83
|
+
Loads the ``EnvConfig`` model.
|
|
84
|
+
"""
|
|
85
|
+
# noinspection PyArgumentList
|
|
86
|
+
return cls(_env_file=filename)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def env_loader(**kwargs) -> EnvConfig:
|
|
90
|
+
"""Loads environment variables based on filetypes or kwargs.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
EnvConfig:
|
|
94
|
+
Returns a reference to the ``EnvConfig`` object.
|
|
95
|
+
"""
|
|
96
|
+
# Default to .env if no kwargs were passed
|
|
97
|
+
if not kwargs:
|
|
98
|
+
return EnvConfig.from_env_file(".env")
|
|
99
|
+
# Look for the kwarg env_file and process accordingly
|
|
100
|
+
if env_file := kwargs.get("env_file") or os.getenv("env_file"):
|
|
101
|
+
env_file = pathlib.Path(env_file)
|
|
102
|
+
assert env_file.is_file(), f"\n\tenv_file: [{env_file.resolve()!r}] does not exist"
|
|
103
|
+
if env_file.suffix.lower() == ".json":
|
|
104
|
+
with env_file.open() as stream:
|
|
105
|
+
env_data = json.load(stream)
|
|
106
|
+
return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
|
|
107
|
+
if env_file.suffix.lower() in (".yaml", ".yml"):
|
|
108
|
+
with env_file.open() as stream:
|
|
109
|
+
env_data = yaml.load(stream, yaml.FullLoader)
|
|
110
|
+
return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
|
|
111
|
+
if not env_file.suffix or env_file.suffix.lower() in (
|
|
112
|
+
".text",
|
|
113
|
+
".txt",
|
|
114
|
+
".env",
|
|
115
|
+
"",
|
|
116
|
+
):
|
|
117
|
+
return EnvConfig.from_env_file(env_file)
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"\n\tUnsupported format for {env_file!r}, " "can be one of (.json, .yaml, .yml, .txt, .text, .env)"
|
|
120
|
+
)
|
|
121
|
+
# Load env config with regular kwargs
|
|
122
|
+
return EnvConfig(**kwargs)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
env: EnvConfig
|
pyobservability/main.py
CHANGED
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
2
|
import pathlib
|
|
3
|
+
import warnings
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import uiauth
|
|
6
|
+
import uvicorn
|
|
6
7
|
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
|
8
|
+
from fastapi.routing import APIRoute, APIWebSocketRoute
|
|
7
9
|
from fastapi.staticfiles import StaticFiles
|
|
8
10
|
from fastapi.templating import Jinja2Templates
|
|
9
11
|
|
|
12
|
+
from pyobservability.config import enums, settings
|
|
10
13
|
from pyobservability.monitor import Monitor
|
|
11
14
|
|
|
12
|
-
dotenv.load_dotenv()
|
|
13
|
-
|
|
14
15
|
LOGGER = logging.getLogger("uvicorn.default")
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
PyObservability = FastAPI(title="PyObservability")
|
|
18
|
+
PyObservability.__name__ = "PyObservability"
|
|
19
|
+
PyObservability.description = "Observability page for nodes running PyNinja"
|
|
20
|
+
|
|
17
21
|
root = pathlib.Path(__file__).parent
|
|
18
22
|
templates_dir = root / "templates"
|
|
19
|
-
static_dir = root / "static"
|
|
20
23
|
templates = Jinja2Templates(directory=templates_dir)
|
|
21
|
-
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
static_dir = root / "static"
|
|
26
|
+
PyObservability.mount("/static", StaticFiles(directory=static_dir), name="static")
|
|
24
27
|
|
|
25
28
|
|
|
26
|
-
@app.get("/")
|
|
27
29
|
async def index(request: Request):
|
|
28
30
|
"""Pass configured targets to the template so frontend can prebuild UI.
|
|
29
31
|
|
|
30
32
|
Args:
|
|
31
33
|
request: FastAPI request object.
|
|
32
34
|
"""
|
|
33
|
-
return templates.TemplateResponse("index.html", {"request": request, "targets":
|
|
35
|
+
return templates.TemplateResponse("index.html", {"request": request, "targets": settings.env.monitor_targets})
|
|
34
36
|
|
|
35
37
|
|
|
36
|
-
@app.websocket("/ws")
|
|
37
38
|
async def websocket_endpoint(websocket: WebSocket):
|
|
38
39
|
"""Websocket endpoint to render the metrics.
|
|
39
40
|
|
|
40
41
|
Args:
|
|
41
42
|
websocket: FastAPI websocket object.
|
|
42
43
|
"""
|
|
44
|
+
monitor = Monitor(targets=settings.env.monitor_targets, poll_interval=settings.env.poll_interval)
|
|
43
45
|
await websocket.accept()
|
|
44
46
|
await monitor.start()
|
|
45
47
|
q = monitor.subscribe()
|
|
@@ -56,5 +58,55 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
|
56
58
|
await websocket.close()
|
|
57
59
|
except Exception as err:
|
|
58
60
|
LOGGER.warning(err)
|
|
59
|
-
pass
|
|
60
61
|
await monitor.stop()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def include_routes():
|
|
65
|
+
if all((settings.env.username, settings.env.password)):
|
|
66
|
+
uiauth.protect(
|
|
67
|
+
app=PyObservability,
|
|
68
|
+
username=settings.env.username,
|
|
69
|
+
password=settings.env.password,
|
|
70
|
+
params=[
|
|
71
|
+
uiauth.Parameters(
|
|
72
|
+
path=enums.APIEndpoints.root,
|
|
73
|
+
function=index,
|
|
74
|
+
methods=["GET"],
|
|
75
|
+
),
|
|
76
|
+
uiauth.Parameters(
|
|
77
|
+
path=enums.APIEndpoints.ws,
|
|
78
|
+
function=websocket_endpoint,
|
|
79
|
+
route=APIWebSocketRoute,
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
warnings.warn("\n\tRunning PyObservability without any protection.", UserWarning)
|
|
85
|
+
PyObservability.routes.append(
|
|
86
|
+
APIRoute(
|
|
87
|
+
path=enums.APIEndpoints.root,
|
|
88
|
+
endpoint=index,
|
|
89
|
+
methods=["GET"],
|
|
90
|
+
include_in_schema=False,
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
PyObservability.routes.append(
|
|
94
|
+
APIWebSocketRoute(
|
|
95
|
+
path=enums.APIEndpoints.ws,
|
|
96
|
+
endpoint=websocket_endpoint,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def start(**kwargs):
|
|
102
|
+
settings.env = settings.env_loader(**kwargs)
|
|
103
|
+
settings.env.monitor_targets = [
|
|
104
|
+
{k: str(v) for k, v in target.model_dump().items()} for target in settings.env.monitor_targets
|
|
105
|
+
]
|
|
106
|
+
include_routes()
|
|
107
|
+
uvicorn_args = dict(
|
|
108
|
+
host=settings.env.host,
|
|
109
|
+
port=settings.env.port,
|
|
110
|
+
app=PyObservability,
|
|
111
|
+
)
|
|
112
|
+
uvicorn.run(**uvicorn_args)
|
pyobservability/monitor.py
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# app/monitor.py
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
import json
|
|
5
4
|
import logging
|
|
6
|
-
import os
|
|
7
5
|
from asyncio import CancelledError
|
|
8
6
|
from typing import Any, Dict, List
|
|
9
7
|
from urllib.parse import urlparse
|
|
@@ -60,42 +58,6 @@ ENDPOINTS = {
|
|
|
60
58
|
}
|
|
61
59
|
|
|
62
60
|
|
|
63
|
-
###############################################################################
|
|
64
|
-
# LOAD TARGETS FROM ENV
|
|
65
|
-
###############################################################################
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def load_targets_from_env() -> List[Dict[str, Any]]:
|
|
69
|
-
"""Loads monitor targets from environment variables and parses it.
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
List[Dict[str, Any]]:
|
|
73
|
-
Returns the parsed list of the monitor targets.
|
|
74
|
-
"""
|
|
75
|
-
raw = os.getenv("MONITOR_TARGETS", "[]")
|
|
76
|
-
|
|
77
|
-
try:
|
|
78
|
-
data = json.loads(raw)
|
|
79
|
-
except Exception:
|
|
80
|
-
data = [raw] if raw else []
|
|
81
|
-
|
|
82
|
-
parsed = []
|
|
83
|
-
|
|
84
|
-
for entry in data:
|
|
85
|
-
if isinstance(entry, str):
|
|
86
|
-
parsed.append({"name": entry, "base_url": entry, "apikey": None})
|
|
87
|
-
elif isinstance(entry, dict):
|
|
88
|
-
parsed.append(
|
|
89
|
-
{
|
|
90
|
-
"name": entry.get("name") or entry["base_url"],
|
|
91
|
-
"base_url": entry["base_url"],
|
|
92
|
-
"apikey": entry.get("apikey"),
|
|
93
|
-
}
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
return parsed
|
|
97
|
-
|
|
98
|
-
|
|
99
61
|
###############################################################################
|
|
100
62
|
# MONITOR CLASS
|
|
101
63
|
###############################################################################
|
|
@@ -103,9 +65,9 @@ def load_targets_from_env() -> List[Dict[str, Any]]:
|
|
|
103
65
|
|
|
104
66
|
class Monitor:
|
|
105
67
|
|
|
106
|
-
def __init__(self, poll_interval: float
|
|
107
|
-
self.targets =
|
|
108
|
-
self.poll_interval =
|
|
68
|
+
def __init__(self, targets: List[Dict[str, str]], poll_interval: float):
|
|
69
|
+
self.targets = targets
|
|
70
|
+
self.poll_interval = poll_interval
|
|
109
71
|
self.sessions: Dict[str, aiohttp.ClientSession] = {}
|
|
110
72
|
self._ws_subscribers: List[asyncio.Queue] = []
|
|
111
73
|
self._task = None
|
|
@@ -115,8 +77,8 @@ class Monitor:
|
|
|
115
77
|
# LIFECYCLE
|
|
116
78
|
############################################################################
|
|
117
79
|
async def start(self):
|
|
118
|
-
for
|
|
119
|
-
self.sessions[
|
|
80
|
+
for target in self.targets:
|
|
81
|
+
self.sessions[target["base_url"]] = aiohttp.ClientSession()
|
|
120
82
|
self._task = asyncio.create_task(self._run_loop())
|
|
121
83
|
|
|
122
84
|
async def stop(self):
|
|
@@ -148,12 +110,8 @@ class Monitor:
|
|
|
148
110
|
############################################################################
|
|
149
111
|
# FETCH WRAPPER
|
|
150
112
|
############################################################################
|
|
151
|
-
async def _fetch(self, session, base_url, ep,
|
|
113
|
+
async def _fetch(self, session, base_url, ep, headers: Dict[str, str], params=None):
|
|
152
114
|
url = base_url.rstrip("/") + ep
|
|
153
|
-
headers = {"accept": "application/json"}
|
|
154
|
-
if apikey:
|
|
155
|
-
headers["Authorization"] = f"Bearer {apikey}"
|
|
156
|
-
|
|
157
115
|
try:
|
|
158
116
|
async with session.get(url, headers=headers, params=params, timeout=10) as resp:
|
|
159
117
|
if resp.status == 200:
|
|
@@ -174,8 +132,9 @@ class Monitor:
|
|
|
174
132
|
############################################################################
|
|
175
133
|
async def _poll_target(self, target: Dict[str, Any]) -> Dict[str, Any]:
|
|
176
134
|
base = target["base_url"]
|
|
177
|
-
apikey = target
|
|
135
|
+
apikey = target["apikey"]
|
|
178
136
|
session = self.sessions[base]
|
|
137
|
+
headers = {"Accept": "application/json", "Authorization": f"Bearer {apikey}"}
|
|
179
138
|
|
|
180
139
|
result = {"name": target["name"], "base_url": base, "metrics": {}}
|
|
181
140
|
|
|
@@ -184,7 +143,7 @@ class Monitor:
|
|
|
184
143
|
|
|
185
144
|
for key, cfg in ENDPOINTS.items():
|
|
186
145
|
tasks[key] = asyncio.create_task(
|
|
187
|
-
self._fetch(session, base, cfg["path"],
|
|
146
|
+
self._fetch(session, base, cfg["path"], headers=headers, params=cfg["params"])
|
|
188
147
|
)
|
|
189
148
|
|
|
190
149
|
# Wait for all endpoints
|
|
@@ -206,7 +165,7 @@ class Monitor:
|
|
|
206
165
|
# POLL ALL HOSTS
|
|
207
166
|
############################################################################
|
|
208
167
|
async def _poll_all(self) -> List[Dict[str, Any]]:
|
|
209
|
-
tasks = [self._poll_target(
|
|
168
|
+
tasks = [self._poll_target(target) for target in self.targets]
|
|
210
169
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
|
211
170
|
out = []
|
|
212
171
|
for r in results:
|
pyobservability/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.0.
|
|
1
|
+
__version__ = "0.0.1"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: PyObservability
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Lightweight OS-agnostic observability UI for PyNinja
|
|
5
|
+
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 TheVickypedia
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/thevickypedia/PyObservability
|
|
29
|
+
Project-URL: Docs, https://thevickypedia.github.io/PyObservability
|
|
30
|
+
Project-URL: Source, https://github.com/thevickypedia/PyObservability
|
|
31
|
+
Project-URL: Bug Tracker, https://github.com/thevickypedia/PyObservability/issues
|
|
32
|
+
Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
|
|
33
|
+
Keywords: PyObservability,observability,system-monitor,PyNinja
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Development Status :: 3 - Alpha
|
|
37
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
38
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
39
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
40
|
+
Classifier: Topic :: System :: Monitoring
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: aiohttp==3.13.*
|
|
45
|
+
Requires-Dist: fastapi==0.122.*
|
|
46
|
+
Requires-Dist: FastAPI-UI-Auth==0.2.1
|
|
47
|
+
Requires-Dist: Jinja2==3.1.*
|
|
48
|
+
Requires-Dist: pydantic==2.12.*
|
|
49
|
+
Requires-Dist: pydantic-settings==2.12.*
|
|
50
|
+
Requires-Dist: python-dotenv==1.2.*
|
|
51
|
+
Requires-Dist: uvicorn[standard]==0.38.*
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: sphinx==5.1.1; extra == "dev"
|
|
54
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
55
|
+
Requires-Dist: recommonmark; extra == "dev"
|
|
56
|
+
Requires-Dist: gitverse; extra == "dev"
|
|
57
|
+
Dynamic: license-file
|
|
58
|
+
|
|
59
|
+
# PyObservability
|
|
60
|
+
|
|
61
|
+
![Python][label-pyversion]
|
|
62
|
+
|
|
63
|
+
**Platform Supported**
|
|
64
|
+
|
|
65
|
+
![Platform][label-platform]
|
|
66
|
+
|
|
67
|
+
**Deployments**
|
|
68
|
+
|
|
69
|
+
[![pypi][label-actions-pypi]][gha_pypi]
|
|
70
|
+
[![notes][label-actions-notes]][gha_notes]
|
|
71
|
+
[![release][label-actions-release]][gha_release]
|
|
72
|
+
|
|
73
|
+
[![Pypi][label-pypi]][pypi]
|
|
74
|
+
[![Pypi-format][label-pypi-format]][pypi-files]
|
|
75
|
+
[![Pypi-status][label-pypi-status]][pypi]
|
|
76
|
+
|
|
77
|
+
## Kick off
|
|
78
|
+
|
|
79
|
+
**Recommendations**
|
|
80
|
+
|
|
81
|
+
- Install `python` [3.11] or above
|
|
82
|
+
- Use a dedicated [virtual environment]
|
|
83
|
+
|
|
84
|
+
**Install PyObservability**
|
|
85
|
+
```shell
|
|
86
|
+
python -m pip install pyobservability
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Initiate - IDE**
|
|
90
|
+
```python
|
|
91
|
+
import pyobservability
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
if __name__ == '__main__':
|
|
95
|
+
pyobservability.start()
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Initiate - CLI**
|
|
99
|
+
```shell
|
|
100
|
+
pyobservability start
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> Use `pyobservability --help` for usage instructions.
|
|
104
|
+
|
|
105
|
+
## Environment Variables
|
|
106
|
+
|
|
107
|
+
<details>
|
|
108
|
+
<summary><strong>Sourcing environment variables from an env file</strong></summary>
|
|
109
|
+
|
|
110
|
+
> _By default, `PyObservability` will look for a `.env` file in the current working directory._
|
|
111
|
+
> _Other file options are supported with a custom kwarg or env var `env_file` pointing to the filepath._
|
|
112
|
+
</details>
|
|
113
|
+
|
|
114
|
+
**Mandatory**
|
|
115
|
+
- **TARGETS** - Target URLs running `PyNinja` in the following format.
|
|
116
|
+
- `TARGETS='[{"name":"node1","base_url":"http://192.168.1.10:8000","apikey":"token1"},{"name":"node2","base_url":"http://192.168.1.11:8000"}]'`
|
|
117
|
+
|
|
118
|
+
**Defaults**
|
|
119
|
+
- **HOST** - Host IP to run PyObservability. Defaults to `127.0.0.1` or `0.0.0.0`
|
|
120
|
+
- **PORT** - Port number to run PyObservability. Defaults to `8080`
|
|
121
|
+
- **INTERVAL** - Polling interval to retrieve server information.
|
|
122
|
+
|
|
123
|
+
**Optional**
|
|
124
|
+
- **USERNAME** - Username to authenticate the monitoring page.
|
|
125
|
+
- **PASSWORD** - Password to authenticate the monitoring page.
|
|
126
|
+
|
|
127
|
+
## License & copyright
|
|
128
|
+
|
|
129
|
+
© Vignesh Rao
|
|
130
|
+
|
|
131
|
+
Licensed under the [MIT License][license]
|
|
132
|
+
|
|
133
|
+
[//]: # (Labels)
|
|
134
|
+
|
|
135
|
+
[label-pypi-package]: https://img.shields.io/badge/Pypi%20Package-pyobservability-blue?style=for-the-badge&logo=Python
|
|
136
|
+
[label-sphinx-doc]: https://img.shields.io/badge/Made%20with-Sphinx-blue?style=for-the-badge&logo=Sphinx
|
|
137
|
+
[label-pyversion]: https://img.shields.io/badge/python-3.11%20%7C%203.12-blue
|
|
138
|
+
[label-platform]: https://img.shields.io/badge/Platform-Linux|macOS|Windows-1f425f.svg
|
|
139
|
+
[label-actions-pypi]: https://github.com/thevickypedia/PyObservability/actions/workflows/python-publish.yml/badge.svg
|
|
140
|
+
[label-pypi]: https://img.shields.io/pypi/v/PyObservability
|
|
141
|
+
[label-pypi-format]: https://img.shields.io/pypi/format/PyObservability
|
|
142
|
+
[label-pypi-status]: https://img.shields.io/pypi/status/PyObservability
|
|
143
|
+
[label-actions-notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml/badge.svg
|
|
144
|
+
[label-actions-release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml/badge.svg
|
|
145
|
+
|
|
146
|
+
[3.11]: https://docs.python.org/3/whatsnew/3.11.html
|
|
147
|
+
[virtual environment]: https://docs.python.org/3/tutorial/venv.html
|
|
148
|
+
[release-notes]: https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
|
|
149
|
+
[gha_pypi]: https://github.com/thevickypedia/PyObservability/actions/workflows/python-publish.yml
|
|
150
|
+
[gha_notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml
|
|
151
|
+
[gha_release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml
|
|
152
|
+
[google-docs]: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
|
|
153
|
+
[pep8]: https://www.python.org/dev/peps/pep-0008/
|
|
154
|
+
[isort]: https://pycqa.github.io/isort/
|
|
155
|
+
[sphinx]: https://www.sphinx-doc.org/en/master/man/sphinx-autogen.html
|
|
156
|
+
[pypi]: https://pypi.org/project/PyObservability
|
|
157
|
+
[pypi-files]: https://pypi.org/project/PyObservability/#files
|
|
158
|
+
[pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/
|
|
159
|
+
[license]: https://github.com/thevickypedia/PyObservability/blob/main/LICENSE
|
|
160
|
+
[runbook]: https://thevickypedia.github.io/PyObservability/
|
|
161
|
+
[samples]: https://github.com/thevickypedia/PyObservability/tree/main/samples
|
|
162
|
+
[PyUdisk]: https://github.com/thevickypedia/PyUdisk
|
|
163
|
+
[PyArchitecture]: https://github.com/thevickypedia/PyArchitecture
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
|
|
2
|
+
pyobservability/main.py,sha256=5_c4Q2D5EgUTQgs6kULBpn0O8dF_k7LacdQhPq1qHnk,3451
|
|
3
|
+
pyobservability/monitor.py,sha256=s2sVp97sLjkkdtL6be82bX5ydu_gBdMSoWxDlmUtpgE,6613
|
|
4
|
+
pyobservability/version.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
|
|
5
|
+
pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
|
|
6
|
+
pyobservability/config/settings.py,sha256=3nPvA-GsmtunUIGcroHqwgSvQ24TmIdINs-QRD_dg2s,3737
|
|
7
|
+
pyobservability/static/app.js,sha256=poc7eReoiRUbyI5JKnPwxSqmSNCuOge4aZKCITFy7eo,13494
|
|
8
|
+
pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
|
|
9
|
+
pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
|
|
10
|
+
pyobservability-0.0.1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
11
|
+
pyobservability-0.0.1.dist-info/METADATA,sha256=BQ4Z6EbkaRWOaY1oc4rf3wpulGoYtUhIfEPDBdCDYBU,6822
|
|
12
|
+
pyobservability-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
pyobservability-0.0.1.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
14
|
+
pyobservability-0.0.1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
15
|
+
pyobservability-0.0.1.dist-info/RECORD,,
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: PyObservability
|
|
3
|
-
Version: 0.0.0a1
|
|
4
|
-
Summary: Lightweight OS-agnostic observability UI for PyNinja
|
|
5
|
-
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
|
-
License: MIT License
|
|
7
|
-
|
|
8
|
-
Copyright (c) 2025 TheVickypedia
|
|
9
|
-
|
|
10
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
-
in the Software without restriction, including without limitation the rights
|
|
13
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
-
furnished to do so, subject to the following conditions:
|
|
16
|
-
|
|
17
|
-
The above copyright notice and this permission notice shall be included in all
|
|
18
|
-
copies or substantial portions of the Software.
|
|
19
|
-
|
|
20
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
-
SOFTWARE.
|
|
27
|
-
|
|
28
|
-
Project-URL: Homepage, https://github.com/thevickypedia/PyObservability
|
|
29
|
-
Project-URL: Docs, https://thevickypedia.github.io/PyObservability
|
|
30
|
-
Project-URL: Source, https://github.com/thevickypedia/PyObservability
|
|
31
|
-
Project-URL: Bug Tracker, https://github.com/thevickypedia/PyObservability/issues
|
|
32
|
-
Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
|
|
33
|
-
Keywords: PyObservability,observability,system-monitor,PyNinja
|
|
34
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
-
Classifier: Programming Language :: Python :: 3
|
|
36
|
-
Classifier: Development Status :: 3 - Alpha
|
|
37
|
-
Classifier: Operating System :: MacOS :: MacOS X
|
|
38
|
-
Classifier: Operating System :: Microsoft :: Windows
|
|
39
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
40
|
-
Classifier: Topic :: System :: Monitoring
|
|
41
|
-
Requires-Python: >=3.11
|
|
42
|
-
Description-Content-Type: text/markdown
|
|
43
|
-
License-File: LICENSE
|
|
44
|
-
Requires-Dist: aiohttp
|
|
45
|
-
Requires-Dist: fastapi
|
|
46
|
-
Requires-Dist: jinja2
|
|
47
|
-
Requires-Dist: python-dotenv
|
|
48
|
-
Requires-Dist: uvicorn[standard]
|
|
49
|
-
Provides-Extra: dev
|
|
50
|
-
Requires-Dist: sphinx==5.1.1; extra == "dev"
|
|
51
|
-
Requires-Dist: pre-commit; extra == "dev"
|
|
52
|
-
Requires-Dist: recommonmark; extra == "dev"
|
|
53
|
-
Requires-Dist: gitverse; extra == "dev"
|
|
54
|
-
Dynamic: license-file
|
|
55
|
-
|
|
56
|
-
# PyObservability
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
pyobservability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
pyobservability/main.py,sha256=XzH2XNuxU9lbFSnQXsJqJ2ytrd6nnzbTf7B0n0UQuW0,1637
|
|
3
|
-
pyobservability/monitor.py,sha256=nnL6odqO3BF-XH1wek0leyMKXw7NB8qrwp6aAh-xJyA,7694
|
|
4
|
-
pyobservability/version.py,sha256=PvT0JaYWREcshCnUiHC2CMpUMuTV359GmoET7cuhPiU,24
|
|
5
|
-
pyobservability/static/app.js,sha256=poc7eReoiRUbyI5JKnPwxSqmSNCuOge4aZKCITFy7eo,13494
|
|
6
|
-
pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
|
|
7
|
-
pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
|
|
8
|
-
pyobservability-0.0.0a1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
9
|
-
pyobservability-0.0.0a1.dist-info/METADATA,sha256=Ze1eq09JsbqSTcpPFIrvMQxKJubLqKxzAxcMhLIzbzw,2663
|
|
10
|
-
pyobservability-0.0.0a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
-
pyobservability-0.0.0a1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
12
|
-
pyobservability-0.0.0a1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|