PyObservability 2.0.0__py3-none-any.whl → 2.1.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/kuma.py +14 -29
- pyobservability/main.py +9 -9
- pyobservability/static/app.js +19 -14
- pyobservability/static/styles.css +6 -1
- pyobservability/templates/index.html +3 -3
- pyobservability/version.py +1 -1
- {pyobservability-2.0.0.dist-info → pyobservability-2.1.1.dist-info}/METADATA +2 -2
- pyobservability-2.1.1.dist-info/RECORD +17 -0
- pyobservability-2.0.0.dist-info/RECORD +0 -17
- {pyobservability-2.0.0.dist-info → pyobservability-2.1.1.dist-info}/WHEEL +0 -0
- {pyobservability-2.0.0.dist-info → pyobservability-2.1.1.dist-info}/entry_points.txt +0 -0
- {pyobservability-2.0.0.dist-info → pyobservability-2.1.1.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-2.0.0.dist-info → pyobservability-2.1.1.dist-info}/top_level.txt +0 -0
pyobservability/kuma.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
|
-
from collections import
|
|
4
|
-
from typing import Any, Dict
|
|
3
|
+
from collections.abc import Generator
|
|
4
|
+
from typing import Any, Dict
|
|
5
5
|
from urllib.parse import urlparse
|
|
6
6
|
|
|
7
7
|
import socketio
|
|
@@ -81,18 +81,16 @@ class UptimeKumaClient:
|
|
|
81
81
|
return self.monitors
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
"""Convert raw API payload into a list of dicts with name, url,
|
|
84
|
+
def extract_monitors(payload: Dict[int, Dict[str, Any]]) -> Generator[Dict[str, Any]]:
|
|
85
|
+
"""Convert raw API payload into a list of dicts with name, url, tags, host.
|
|
86
86
|
|
|
87
87
|
Args:
|
|
88
88
|
payload: Raw payload from Uptime Kuma server.
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
Yields:
|
|
91
|
+
Dict[str, Any]:
|
|
92
|
+
Monitors with relevant fields.
|
|
93
93
|
"""
|
|
94
|
-
monitors = []
|
|
95
|
-
|
|
96
94
|
grouped = {}
|
|
97
95
|
for monitor in payload.values():
|
|
98
96
|
if children_ids := monitor.get("childrenIDs"):
|
|
@@ -104,23 +102,10 @@ async def extract_monitors(payload: Dict[int, Dict[str, Any]]) -> List[Dict[str,
|
|
|
104
102
|
host = urlparse(url).hostname if url else None
|
|
105
103
|
if not host:
|
|
106
104
|
continue
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
)
|
|
116
|
-
return monitors
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
async def group_by_host(monitors: List[Dict[str, Any]]) -> Dict[str, Any]:
|
|
120
|
-
"""Group monitors by host."""
|
|
121
|
-
grouped = defaultdict(list)
|
|
122
|
-
|
|
123
|
-
for monitor in monitors:
|
|
124
|
-
grouped[monitor["host"]].append(monitor)
|
|
125
|
-
|
|
126
|
-
return dict(grouped)
|
|
105
|
+
yield {
|
|
106
|
+
"name": monitor.get("name"),
|
|
107
|
+
"parent": grouped.get(monitor.get("id")),
|
|
108
|
+
"url": url,
|
|
109
|
+
"host": host,
|
|
110
|
+
"tags": [tag.get("name") for tag in monitor.get("tags", []) if "name" in tag],
|
|
111
|
+
}
|
pyobservability/main.py
CHANGED
|
@@ -13,7 +13,7 @@ from fastapi.staticfiles import StaticFiles
|
|
|
13
13
|
from fastapi.templating import Jinja2Templates
|
|
14
14
|
|
|
15
15
|
from pyobservability.config import enums, settings
|
|
16
|
-
from pyobservability.kuma import UptimeKumaClient, extract_monitors
|
|
16
|
+
from pyobservability.kuma import UptimeKumaClient, extract_monitors
|
|
17
17
|
from pyobservability.transport import websocket_endpoint
|
|
18
18
|
from pyobservability.version import __version__
|
|
19
19
|
|
|
@@ -41,8 +41,8 @@ async def index(request: Request):
|
|
|
41
41
|
TemplateResponse:
|
|
42
42
|
Rendered HTML template with targets and version.
|
|
43
43
|
"""
|
|
44
|
-
kuma_data = {} if all((settings.env.kuma_url, settings.env.kuma_username, settings.env.kuma_password)) else None
|
|
45
|
-
args = dict(request=request,
|
|
44
|
+
kuma_data = [{}] if all((settings.env.kuma_url, settings.env.kuma_username, settings.env.kuma_password)) else None
|
|
45
|
+
args = dict(request=request, kuma_data=kuma_data, targets=settings.env.targets, version=__version__)
|
|
46
46
|
if settings.env.username and settings.env.password:
|
|
47
47
|
args["logout"] = uiauth.enums.APIEndpoints.fastapi_logout.value
|
|
48
48
|
return templates.TemplateResponse("index.html", args)
|
|
@@ -52,20 +52,20 @@ async def kuma():
|
|
|
52
52
|
"""Kuma endpoint to retrieve monitors from Kuma server.
|
|
53
53
|
|
|
54
54
|
Returns:
|
|
55
|
-
Dict[str, Any]:
|
|
56
|
-
|
|
55
|
+
List[Dict[str, Any]]:
|
|
56
|
+
List of monitors from Kuma server after filtering the required fields.
|
|
57
57
|
"""
|
|
58
58
|
try:
|
|
59
59
|
kuma_data = UptimeKumaClient().get_monitors()
|
|
60
|
-
LOGGER.info("Retrieved
|
|
60
|
+
LOGGER.info("Retrieved monitors from kuma server - %d found.", len(kuma_data))
|
|
61
61
|
except RuntimeError:
|
|
62
62
|
raise HTTPException(
|
|
63
63
|
status_code=HTTPStatus.SERVICE_UNAVAILABLE.real,
|
|
64
64
|
detail="Unable to retrieve data from kuma server.",
|
|
65
65
|
)
|
|
66
|
-
|
|
67
|
-
LOGGER.info("
|
|
68
|
-
return
|
|
66
|
+
monitors = list(extract_monitors(kuma_data))
|
|
67
|
+
LOGGER.info("Processed [%d] monitors for UI payload.", len(monitors))
|
|
68
|
+
return monitors
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
async def health() -> Dict[str, str]:
|
pyobservability/static/app.js
CHANGED
|
@@ -366,26 +366,24 @@
|
|
|
366
366
|
});
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
function
|
|
369
|
+
function normalizeKumaMap(monitors) {
|
|
370
370
|
const rows = [];
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
URL: `<a href="${svc.url}" target="_blank">${svc.url}</a>`
|
|
379
|
-
});
|
|
371
|
+
monitors.forEach(monitor => {
|
|
372
|
+
rows.push({
|
|
373
|
+
Node: monitor.host,
|
|
374
|
+
Name: monitor.name,
|
|
375
|
+
Parent: monitor.parent || "—",
|
|
376
|
+
Tags: (monitor.tags || []).join(", "),
|
|
377
|
+
URL: `<a href="${monitor.url}" target="_blank">${monitor.url}</a>`
|
|
380
378
|
});
|
|
381
379
|
});
|
|
382
380
|
return rows;
|
|
383
381
|
}
|
|
384
382
|
|
|
385
383
|
function renderEndpoints() {
|
|
386
|
-
if (!window.
|
|
384
|
+
if (!window.KUMA_DATA) return;
|
|
387
385
|
|
|
388
|
-
const rows =
|
|
386
|
+
const rows = normalizeKumaMap(window.KUMA_DATA);
|
|
389
387
|
const columns = ["Node", "Name", "Parent", "Tags", "URL"];
|
|
390
388
|
|
|
391
389
|
PAG_ENDPOINTS.setData(rows, columns);
|
|
@@ -827,6 +825,9 @@
|
|
|
827
825
|
function resetUI() {
|
|
828
826
|
firstMessage = true;
|
|
829
827
|
hideSpinners();
|
|
828
|
+
// Disable Kuma tab during loading if present
|
|
829
|
+
if (kumaTab) { kumaTab.disabled = true; }
|
|
830
|
+
|
|
830
831
|
const EMPTY_DATA = Array(MAX_POINTS).fill(null);
|
|
831
832
|
const EMPTY_LABELS = Array(MAX_POINTS).fill("");
|
|
832
833
|
|
|
@@ -940,6 +941,8 @@
|
|
|
940
941
|
if (firstMessage) {
|
|
941
942
|
hideSpinners();
|
|
942
943
|
firstMessage = false;
|
|
944
|
+
// Enable Kuma tab once initial data is loaded
|
|
945
|
+
if (kumaTab) { kumaTab.disabled = false; }
|
|
943
946
|
}
|
|
944
947
|
|
|
945
948
|
const now = new Date().toLocaleTimeString();
|
|
@@ -1197,12 +1200,12 @@
|
|
|
1197
1200
|
|
|
1198
1201
|
try {
|
|
1199
1202
|
const response = await fetch('/kuma');
|
|
1200
|
-
if (!response.ok) throw new Error('Failed to fetch
|
|
1203
|
+
if (!response.ok) throw new Error('Failed to fetch kuma map');
|
|
1201
1204
|
|
|
1202
1205
|
kumaMapData = await response.json();
|
|
1203
1206
|
kumaMapLoaded = true;
|
|
1204
1207
|
|
|
1205
|
-
allKumaRows =
|
|
1208
|
+
allKumaRows = normalizeKumaMap(kumaMapData);
|
|
1206
1209
|
PAG_KUMA_TAB.setData(allKumaRows, ["Node", "Name", "Parent", "Tags", "URL"]);
|
|
1207
1210
|
} catch (err) {
|
|
1208
1211
|
console.error("Error loading Kuma map:", err);
|
|
@@ -1273,5 +1276,7 @@
|
|
|
1273
1276
|
|
|
1274
1277
|
// Initialize nodes view by default
|
|
1275
1278
|
document.body.classList.add('nodes-view');
|
|
1279
|
+
// Disable until first metrics load
|
|
1280
|
+
if (kumaTab) { kumaTab.disabled = true; }
|
|
1276
1281
|
initWebSocket();
|
|
1277
1282
|
})();
|
|
@@ -51,7 +51,7 @@ html, body {
|
|
|
51
51
|
transition: all 0.2s;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
.tab-btn:hover {
|
|
54
|
+
.tab-btn:hover:not(:disabled) {
|
|
55
55
|
color: #e6eef8;
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -60,6 +60,11 @@ html, body {
|
|
|
60
60
|
border-bottom-color: var(--accent);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
.tab-btn:disabled {
|
|
64
|
+
opacity: 0.4;
|
|
65
|
+
cursor: not-allowed;
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
.brand {
|
|
64
69
|
font-weight: 700;
|
|
65
70
|
font-size: 18px;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<header class="topbar">
|
|
20
20
|
<div class="brand">Node Monitor</div>
|
|
21
21
|
|
|
22
|
-
{% if
|
|
22
|
+
{% if kuma_data is not none %}
|
|
23
23
|
<div class="tab-navigation">
|
|
24
24
|
<button id="nodes-tab" class="tab-btn active">Nodes</button>
|
|
25
25
|
<button id="kuma-tab" class="tab-btn">Uptime Kuma</button>
|
|
@@ -293,9 +293,9 @@
|
|
|
293
293
|
window.MONITOR_TARGETS = {{ targets | tojson }};
|
|
294
294
|
</script>
|
|
295
295
|
|
|
296
|
-
{% if
|
|
296
|
+
{% if kuma_data is not none %}
|
|
297
297
|
<script>
|
|
298
|
-
window.
|
|
298
|
+
window.KUMA_DATA = {{ kuma_data | tojson }};
|
|
299
299
|
</script>
|
|
300
300
|
{% endif %}
|
|
301
301
|
|
pyobservability/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.1.1"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyObservability
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Lightweight OS-agnostic observability UI for PyNinja
|
|
5
5
|
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -43,7 +43,7 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
License-File: LICENSE
|
|
44
44
|
Requires-Dist: aiohttp==3.13.*
|
|
45
45
|
Requires-Dist: fastapi==0.128.*
|
|
46
|
-
Requires-Dist: FastAPI-UI-Auth==0.2
|
|
46
|
+
Requires-Dist: FastAPI-UI-Auth==0.2.*
|
|
47
47
|
Requires-Dist: Jinja2==3.1.*
|
|
48
48
|
Requires-Dist: pydantic==2.12.*
|
|
49
49
|
Requires-Dist: pydantic-settings==2.12.*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
|
|
2
|
+
pyobservability/kuma.py,sha256=4sB1M7-exzrObGGIh1yYQ22P40kHwsTfv4G1Xj9YYWw,3260
|
|
3
|
+
pyobservability/main.py,sha256=sMcQRh4mmivs8Xssu_PcP698oc1AzmMt8Tv19BSdmdo,6152
|
|
4
|
+
pyobservability/monitor.py,sha256=i_Xf_DB-qLOp1b9wryekjwHIM8AnMrGTkuEg7e08bcM,7539
|
|
5
|
+
pyobservability/transport.py,sha256=S-84mgf-9yMj0H7VSAmueW9yosX_1XxdyNJC2EuQHQQ,8493
|
|
6
|
+
pyobservability/version.py,sha256=zPJIgPGcoSNiD0qme18OnYJYE3A9VVytlhO-V5DaAW0,22
|
|
7
|
+
pyobservability/config/enums.py,sha256=rQZh2Q4-9ItQTQgxsYjqw-jNePpWHhvQUrbrQJBG5CI,313
|
|
8
|
+
pyobservability/config/settings.py,sha256=aCz1tZEg8fUA5gHCCDN_m3fgrH1axz2fvdalj4bszGs,6121
|
|
9
|
+
pyobservability/static/app.js,sha256=rTj3WWTgkfHJR3qf19flGUJ-9OAeFtVX6Nm5vw51T0w,48862
|
|
10
|
+
pyobservability/static/styles.css,sha256=pSVrSpeeDHaNsqI-WI3PrtnaPRiuU5rIrpB8g6OlHBc,9486
|
|
11
|
+
pyobservability/templates/index.html,sha256=aqvHrxP_D2gSxYmsUkNMi1QH07pQNXh4VuXc51KBo5c,11826
|
|
12
|
+
pyobservability-2.1.1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
13
|
+
pyobservability-2.1.1.dist-info/METADATA,sha256=1iEH3chJwsf8T2FKyXLA3xn4x6coVytWSSr83o_9G1w,7026
|
|
14
|
+
pyobservability-2.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
+
pyobservability-2.1.1.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
16
|
+
pyobservability-2.1.1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
17
|
+
pyobservability-2.1.1.dist-info/RECORD,,
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
|
|
2
|
-
pyobservability/kuma.py,sha256=RL6ZsK6ZE3vUufsUQ3r4gnH8uka0k8ARd41GP5YozbU,3634
|
|
3
|
-
pyobservability/main.py,sha256=EbP6xkOvj2cV1cOEmmq6E6QsLOhbvKI_jdpfcWjSz-M,6124
|
|
4
|
-
pyobservability/monitor.py,sha256=i_Xf_DB-qLOp1b9wryekjwHIM8AnMrGTkuEg7e08bcM,7539
|
|
5
|
-
pyobservability/transport.py,sha256=S-84mgf-9yMj0H7VSAmueW9yosX_1XxdyNJC2EuQHQQ,8493
|
|
6
|
-
pyobservability/version.py,sha256=_7OlQdbVkK4jad0CLdpI0grT-zEAb-qgFmH5mFzDXiA,22
|
|
7
|
-
pyobservability/config/enums.py,sha256=rQZh2Q4-9ItQTQgxsYjqw-jNePpWHhvQUrbrQJBG5CI,313
|
|
8
|
-
pyobservability/config/settings.py,sha256=aCz1tZEg8fUA5gHCCDN_m3fgrH1axz2fvdalj4bszGs,6121
|
|
9
|
-
pyobservability/static/app.js,sha256=lE-Oybq50FNr_0SrZJFiJihiXyIcyhw7-SNbGA81ySQ,48675
|
|
10
|
-
pyobservability/static/styles.css,sha256=P6Xg-IAXO3WNeBLGH9Q5HAdNeDMRbFcM5P_cq60Jf00,9405
|
|
11
|
-
pyobservability/templates/index.html,sha256=WQNOT_uHfAvVqOnYc1olYdyrjM11tDpeKwTkbPQ853Q,11834
|
|
12
|
-
pyobservability-2.0.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
13
|
-
pyobservability-2.0.0.dist-info/METADATA,sha256=hu_7k6rRxUXitpfLhkAB6GZxMYQNDeqGaQAHJDKa0Ss,7026
|
|
14
|
-
pyobservability-2.0.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
15
|
-
pyobservability-2.0.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
16
|
-
pyobservability-2.0.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
17
|
-
pyobservability-2.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|