PyObservability 0.0.0a1__py3-none-any.whl → 0.0.0a2__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/settings.py +108 -0
- pyobservability/main.py +40 -12
- pyobservability/monitor.py +10 -51
- pyobservability/version.py +1 -1
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.0a2.dist-info}/METADATA +8 -6
- pyobservability-0.0.0a2.dist-info/RECORD +14 -0
- pyobservability-0.0.0a2.dist-info/entry_points.txt +2 -0
- pyobservability-0.0.0a1.dist-info/RECORD +0 -12
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.0a2.dist-info}/WHEEL +0 -0
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.0a2.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-0.0.0a1.dist-info → pyobservability-0.0.0a2.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,108 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pathlib
|
|
3
|
+
import socket
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from pydantic import BaseModel, HttpUrl, PositiveInt
|
|
8
|
+
from pydantic_settings import BaseSettings
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PydanticEnvConfig(BaseSettings):
|
|
12
|
+
"""Pydantic BaseSettings with custom order for loading environment variables.
|
|
13
|
+
|
|
14
|
+
>>> PydanticEnvConfig
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def settings_customise_sources(
|
|
20
|
+
cls,
|
|
21
|
+
settings_cls,
|
|
22
|
+
init_settings,
|
|
23
|
+
env_settings,
|
|
24
|
+
dotenv_settings,
|
|
25
|
+
file_secret_settings,
|
|
26
|
+
):
|
|
27
|
+
"""Order: dotenv, env, init, secrets files."""
|
|
28
|
+
return dotenv_settings, env_settings, init_settings, file_secret_settings
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class MonitorTarget(BaseModel):
|
|
32
|
+
name: str
|
|
33
|
+
base_url: HttpUrl
|
|
34
|
+
apikey: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class EnvConfig(PydanticEnvConfig):
|
|
38
|
+
"""Configuration settings for the server.
|
|
39
|
+
|
|
40
|
+
>>> EnvConfig
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
host: str = socket.gethostbyname("localhost") or "0.0.0.0"
|
|
45
|
+
port: PositiveInt = 8080
|
|
46
|
+
|
|
47
|
+
monitor_targets: List[MonitorTarget]
|
|
48
|
+
poll_interval: PositiveInt = 3
|
|
49
|
+
|
|
50
|
+
class Config:
|
|
51
|
+
"""Environment variables configuration."""
|
|
52
|
+
|
|
53
|
+
env_prefix = ""
|
|
54
|
+
extra = "forbid"
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def from_env_file(cls, filename: pathlib.Path) -> "EnvConfig":
|
|
58
|
+
"""Create an instance of EnvConfig from environment file.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
filename: Name of the env file.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
EnvConfig:
|
|
65
|
+
Loads the ``EnvConfig`` model.
|
|
66
|
+
"""
|
|
67
|
+
# noinspection PyArgumentList
|
|
68
|
+
return cls(_env_file=filename)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def env_loader(**kwargs) -> EnvConfig:
|
|
72
|
+
"""Loads environment variables based on filetypes or kwargs.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
EnvConfig:
|
|
76
|
+
Returns a reference to the ``EnvConfig`` object.
|
|
77
|
+
"""
|
|
78
|
+
# Default to .env if no kwargs were passed
|
|
79
|
+
if not kwargs:
|
|
80
|
+
return EnvConfig.from_env_file(".env")
|
|
81
|
+
# Look for the kwarg env_file and process accordingly
|
|
82
|
+
if env_file := kwargs.get("env_file"):
|
|
83
|
+
env_file = pathlib.Path(env_file)
|
|
84
|
+
assert env_file.is_file(), f"\n\tenv_file: [{env_file.resolve()!r}] does not exist"
|
|
85
|
+
if env_file.suffix.lower() == ".json":
|
|
86
|
+
with env_file.open() as stream:
|
|
87
|
+
env_data = json.load(stream)
|
|
88
|
+
return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
|
|
89
|
+
elif env_file.suffix.lower() in (".yaml", ".yml"):
|
|
90
|
+
with env_file.open() as stream:
|
|
91
|
+
env_data = yaml.load(stream, yaml.FullLoader)
|
|
92
|
+
return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
|
|
93
|
+
elif not env_file.suffix or env_file.suffix.lower() in (
|
|
94
|
+
".text",
|
|
95
|
+
".txt",
|
|
96
|
+
".env",
|
|
97
|
+
"",
|
|
98
|
+
):
|
|
99
|
+
return EnvConfig.from_env_file(env_file)
|
|
100
|
+
else:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"\n\tUnsupported format for {env_file!r}, " "can be one of (.json, .yaml, .yml, .txt, .text, .env)"
|
|
103
|
+
)
|
|
104
|
+
# Load env config with regular kwargs
|
|
105
|
+
return EnvConfig(**kwargs)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
env: EnvConfig
|
pyobservability/main.py
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import os
|
|
3
2
|
import pathlib
|
|
4
3
|
|
|
5
|
-
import
|
|
4
|
+
import uvicorn
|
|
6
5
|
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
|
6
|
+
from fastapi.routing import APIRoute, APIWebSocketRoute
|
|
7
7
|
from fastapi.staticfiles import StaticFiles
|
|
8
8
|
from fastapi.templating import Jinja2Templates
|
|
9
9
|
|
|
10
|
+
from pyobservability.config import settings
|
|
10
11
|
from pyobservability.monitor import Monitor
|
|
11
12
|
|
|
12
|
-
dotenv.load_dotenv()
|
|
13
|
-
|
|
14
13
|
LOGGER = logging.getLogger("uvicorn.default")
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
PyObservability = FastAPI(title="PyObservability")
|
|
16
|
+
PyObservability.__name__ = "PyObservability"
|
|
17
|
+
PyObservability.description = "Observability page for nodes running PyNinja"
|
|
18
|
+
|
|
17
19
|
root = pathlib.Path(__file__).parent
|
|
18
20
|
templates_dir = root / "templates"
|
|
19
|
-
static_dir = root / "static"
|
|
20
21
|
templates = Jinja2Templates(directory=templates_dir)
|
|
21
|
-
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
static_dir = root / "static"
|
|
24
|
+
PyObservability.mount("/static", StaticFiles(directory=static_dir), name="static")
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
@app.get("/")
|
|
27
27
|
async def index(request: Request):
|
|
28
28
|
"""Pass configured targets to the template so frontend can prebuild UI.
|
|
29
29
|
|
|
30
30
|
Args:
|
|
31
31
|
request: FastAPI request object.
|
|
32
32
|
"""
|
|
33
|
-
return templates.TemplateResponse("index.html", {"request": request, "targets":
|
|
33
|
+
return templates.TemplateResponse("index.html", {"request": request, "targets": settings.env.monitor_targets})
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
@app.websocket("/ws")
|
|
37
36
|
async def websocket_endpoint(websocket: WebSocket):
|
|
38
37
|
"""Websocket endpoint to render the metrics.
|
|
39
38
|
|
|
40
39
|
Args:
|
|
41
40
|
websocket: FastAPI websocket object.
|
|
42
41
|
"""
|
|
42
|
+
monitor = Monitor(targets=settings.env.monitor_targets, poll_interval=settings.env.poll_interval)
|
|
43
43
|
await websocket.accept()
|
|
44
44
|
await monitor.start()
|
|
45
45
|
q = monitor.subscribe()
|
|
@@ -56,5 +56,33 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
|
56
56
|
await websocket.close()
|
|
57
57
|
except Exception as err:
|
|
58
58
|
LOGGER.warning(err)
|
|
59
|
-
pass
|
|
60
59
|
await monitor.stop()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
PyObservability.routes.append(
|
|
63
|
+
APIRoute(
|
|
64
|
+
path="/", # enums.APIEndpoints.root,
|
|
65
|
+
endpoint=index,
|
|
66
|
+
methods=["GET"],
|
|
67
|
+
include_in_schema=False,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
PyObservability.routes.append(
|
|
71
|
+
APIWebSocketRoute(
|
|
72
|
+
path="/ws",
|
|
73
|
+
endpoint=websocket_endpoint,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def start(**kwargs):
|
|
79
|
+
settings.env = settings.env_loader(**kwargs)
|
|
80
|
+
settings.env.monitor_targets = [
|
|
81
|
+
{k: str(v) for k, v in target.model_dump().items()} for target in settings.env.monitor_targets
|
|
82
|
+
]
|
|
83
|
+
uvicorn_args = dict(
|
|
84
|
+
host=settings.env.host,
|
|
85
|
+
port=settings.env.port,
|
|
86
|
+
app=PyObservability,
|
|
87
|
+
)
|
|
88
|
+
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.0a2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyObservability
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.0a2
|
|
4
4
|
Summary: Lightweight OS-agnostic observability UI for PyNinja
|
|
5
5
|
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -41,11 +41,13 @@ Classifier: Topic :: System :: Monitoring
|
|
|
41
41
|
Requires-Python: >=3.11
|
|
42
42
|
Description-Content-Type: text/markdown
|
|
43
43
|
License-File: LICENSE
|
|
44
|
-
Requires-Dist: aiohttp
|
|
45
|
-
Requires-Dist: fastapi
|
|
46
|
-
Requires-Dist:
|
|
47
|
-
Requires-Dist:
|
|
48
|
-
Requires-Dist:
|
|
44
|
+
Requires-Dist: aiohttp==3.13.*
|
|
45
|
+
Requires-Dist: fastapi==0.122.*
|
|
46
|
+
Requires-Dist: Jinja2==3.1.*
|
|
47
|
+
Requires-Dist: pydantic==2.12.*
|
|
48
|
+
Requires-Dist: pydantic-settings==2.12.*
|
|
49
|
+
Requires-Dist: python-dotenv==1.2.*
|
|
50
|
+
Requires-Dist: uvicorn[standard]==0.38.*
|
|
49
51
|
Provides-Extra: dev
|
|
50
52
|
Requires-Dist: sphinx==5.1.1; extra == "dev"
|
|
51
53
|
Requires-Dist: pre-commit; extra == "dev"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
|
|
2
|
+
pyobservability/main.py,sha256=m3jNBQ7B495d1Pk_Fcdy3AQbNts2K8iFwDQDKV1pB0M,2527
|
|
3
|
+
pyobservability/monitor.py,sha256=s2sVp97sLjkkdtL6be82bX5ydu_gBdMSoWxDlmUtpgE,6613
|
|
4
|
+
pyobservability/version.py,sha256=za-UuO_D1PzxYCprpyh75AnwaFhntPuAZpHRqS1fIxc,24
|
|
5
|
+
pyobservability/config/settings.py,sha256=53dYdfO5SbmHQ4cLzPM2JQvrU2Lw70vBghlhiLy28ZI,3013
|
|
6
|
+
pyobservability/static/app.js,sha256=poc7eReoiRUbyI5JKnPwxSqmSNCuOge4aZKCITFy7eo,13494
|
|
7
|
+
pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
|
|
8
|
+
pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
|
|
9
|
+
pyobservability-0.0.0a2.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
10
|
+
pyobservability-0.0.0a2.dist-info/METADATA,sha256=y74dxaDkt1pTwgqFmkNj7bemJqOPqKUjfHBHPwWh0UA,2775
|
|
11
|
+
pyobservability-0.0.0a2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
pyobservability-0.0.0a2.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
13
|
+
pyobservability-0.0.0a2.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
14
|
+
pyobservability-0.0.0a2.dist-info/RECORD,,
|
|
@@ -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
|