PyObservability 0.1.0__py3-none-any.whl → 1.0.0__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/config/settings.py +31 -2
- pyobservability/main.py +11 -34
- pyobservability/monitor.py +136 -81
- pyobservability/static/app.js +322 -247
- pyobservability/static/styles.css +60 -7
- pyobservability/templates/index.html +16 -10
- pyobservability/transport.py +71 -0
- pyobservability/version.py +1 -1
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/METADATA +1 -1
- pyobservability-1.0.0.dist-info/RECORD +16 -0
- pyobservability-0.1.0.dist-info/RECORD +0 -15
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/WHEEL +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/entry_points.txt +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-0.1.0.dist-info → pyobservability-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
* { box-sizing: border-box; }
|
|
10
|
-
html,body {
|
|
10
|
+
html,body {
|
|
11
|
+
height:100%; margin:0; font-family: Inter, system-ui, -apple-system,
|
|
12
|
+
"Segoe UI", Roboto, "Helvetica Neue", Arial; background:var(--bg); color:#e6eef8;
|
|
13
|
+
}
|
|
11
14
|
|
|
15
|
+
.hidden { display: none !important; }
|
|
12
16
|
.topbar {
|
|
13
17
|
display:flex; align-items:center; justify-content:space-between;
|
|
14
18
|
padding:12px 20px; background:linear-gradient(90deg,#071028,#0b1530); border-bottom:1px solid rgba(255,255,255,0.02);
|
|
@@ -16,16 +20,20 @@ html,body { height:100%; margin:0; font-family: Inter, system-ui, -apple-system,
|
|
|
16
20
|
.brand { font-weight:700; font-size:18px; }
|
|
17
21
|
.controls { display:flex; gap:8px; align-items:center; }
|
|
18
22
|
|
|
19
|
-
.controls select, .controls button {
|
|
23
|
+
.controls select, .controls button {
|
|
24
|
+
background:var(--panel); color:inherit; border:1px solid rgba(255,255,255,0.04); padding:6px 8px; border-radius:6px;
|
|
25
|
+
}
|
|
20
26
|
.controls label { font-size:14px; color:var(--muted); margin-right:6px; }
|
|
21
27
|
|
|
22
28
|
.container { padding:18px; display:flex; flex-direction:column; gap:16px; max-width:1200px; margin:0 auto; }
|
|
23
29
|
|
|
24
|
-
.meta-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
.meta-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; align-items: stretch; }
|
|
31
|
+
.meta-card {
|
|
32
|
+
display: flex; flex-direction: column; background:var(--panel); padding:12px; border-radius:8px; width:1fr;
|
|
33
|
+
min-width:120px; flex:1; box-shadow:0 4px 12px rgba(2,6,23,0.6);
|
|
34
|
+
}
|
|
27
35
|
.meta-title { font-size:12px; color:var(--muted); }
|
|
28
|
-
.meta-value { margin-top:6px; font-weight:600; font-size:16px; }
|
|
36
|
+
.meta-value { flex: 1; margin-top:6px; font-weight:600; font-size:16px; }
|
|
29
37
|
.meta-value.pre { white-space:pre-wrap; font-family:monospace; font-size:13px; }
|
|
30
38
|
|
|
31
39
|
.charts-row { display:flex; gap:12px; }
|
|
@@ -48,7 +56,9 @@ html,body { height:100%; margin:0; font-family: Inter, system-ui, -apple-system,
|
|
|
48
56
|
.tables-row { display:grid; grid-template-columns:1fr; grid-auto-rows:auto; gap:12px; }
|
|
49
57
|
.table { width:100%; border-collapse:collapse; font-size:13px; }
|
|
50
58
|
.table th, .table td { padding:8px; border-bottom:1px solid rgba(255,255,255,0.03); text-align:left; }
|
|
51
|
-
.pre {
|
|
59
|
+
.pre {
|
|
60
|
+
background:rgba(255,255,255,0.02); padding:8px; border-radius:6px; overflow:auto; white-space:pre-wrap; font-family:monospace; font-size:13px;
|
|
61
|
+
}
|
|
52
62
|
.list { padding-left:14px; margin:8px 0; }
|
|
53
63
|
.footer { padding:12px 20px; font-size:12px; color:var(--muted); text-align:center; border-top:1px solid rgba(255,255,255,0.02); margin-top:20px; }
|
|
54
64
|
.service-controls { display:flex; gap:8px; padding-bottom:8px; }
|
|
@@ -61,3 +71,46 @@ input#proc-filter { flex:1; padding:6px 8px; background:var(--panel); border:1px
|
|
|
61
71
|
.details-row { flex-direction:column; }
|
|
62
72
|
.meta-row { grid-template-columns: 1fr; }
|
|
63
73
|
}
|
|
74
|
+
|
|
75
|
+
/* ---------------- SPINNER ------------------- */
|
|
76
|
+
.loading-overlay {
|
|
77
|
+
position: absolute;
|
|
78
|
+
inset: 0;
|
|
79
|
+
background: rgba(0,0,0,0.65); /* Darker so spinner is visible */
|
|
80
|
+
backdrop-filter: blur(2px); /* Add blur to ensure visual separation */
|
|
81
|
+
display: flex;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
align-items: center;
|
|
84
|
+
z-index: 50;
|
|
85
|
+
border-radius: 8px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.spinner {
|
|
89
|
+
width: 32px;
|
|
90
|
+
height: 32px;
|
|
91
|
+
border: 4px solid rgba(255,255,255,0.25);
|
|
92
|
+
border-top: 4px solid white; /* More contrast */
|
|
93
|
+
border-radius: 50%;
|
|
94
|
+
animation: spin 0.7s linear infinite;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes spin {
|
|
98
|
+
to { transform: rotate(360deg); }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* --------------- PAGINATION ------------------ */
|
|
102
|
+
.pagination { display: flex; justify-content: center; gap: 6px; padding: 6px; margin-top: 6px; font-size: 13px; }
|
|
103
|
+
|
|
104
|
+
.pagination button {
|
|
105
|
+
background: var(--panel); color: inherit; border: 1px solid rgba(255,255,255,0.05); padding: 4px 8px; border-radius: 4px; cursor: pointer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.pagination button.active {
|
|
109
|
+
background: var(--accent);
|
|
110
|
+
color: #000;
|
|
111
|
+
}
|
|
112
|
+
.pagination-info {
|
|
113
|
+
font-size: 13px;
|
|
114
|
+
margin-bottom: 4px;
|
|
115
|
+
opacity: 0.85;
|
|
116
|
+
}
|
|
@@ -23,32 +23,32 @@
|
|
|
23
23
|
<section class="meta-row">
|
|
24
24
|
<div class="meta-card">
|
|
25
25
|
<div class="meta-title">System</div>
|
|
26
|
-
<div id="system" class="meta-value pre"
|
|
26
|
+
<div id="system" class="meta-value pre"></div>
|
|
27
27
|
</div>
|
|
28
28
|
|
|
29
29
|
<div class="meta-card">
|
|
30
30
|
<div class="meta-title">IP</div>
|
|
31
|
-
<div id="ip-info" class="meta-value pre"
|
|
31
|
+
<div id="ip-info" class="meta-value pre"></div>
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
<div class="meta-card">
|
|
35
35
|
<div class="meta-title">GPU / CPU Model</div>
|
|
36
|
-
<div id="processor" class="meta-value pre"
|
|
36
|
+
<div id="processor" class="meta-value pre"></div>
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
39
|
<div class="meta-card">
|
|
40
40
|
<div class="meta-title">CPU Load (1/5/15)</div>
|
|
41
|
-
<div id="cpuload" class="meta-value pre"
|
|
41
|
+
<div id="cpuload" class="meta-value pre"></div>
|
|
42
42
|
</div>
|
|
43
43
|
|
|
44
44
|
<div class="meta-card">
|
|
45
45
|
<div class="meta-title">Disk ( / )</div>
|
|
46
|
-
<div id="disk" class="meta-value pre"
|
|
46
|
+
<div id="disk" class="meta-value pre"></div>
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
<div class="meta-card">
|
|
50
50
|
<div class="meta-title">Memory</div>
|
|
51
|
-
<div id="memory" class="meta-value pre"
|
|
51
|
+
<div id="memory" class="meta-value pre"></div>
|
|
52
52
|
</div>
|
|
53
53
|
</section>
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
<div class="panel-header">
|
|
58
58
|
<h3>CPU (Average)</h3>
|
|
59
59
|
<div class="panel-actions">
|
|
60
|
-
<label><input type="checkbox" id="show-cores" /> Show per-core</label>
|
|
60
|
+
<label><input type="checkbox" id="show-cores" checked="true" /> Show per-core</label>
|
|
61
61
|
</div>
|
|
62
62
|
</div>
|
|
63
63
|
<canvas id="cpu-avg-chart" class="chart"></canvas>
|
|
@@ -83,7 +83,12 @@
|
|
|
83
83
|
|
|
84
84
|
<section class="tables-row">
|
|
85
85
|
<div class="panel">
|
|
86
|
-
<div class="panel-header"
|
|
86
|
+
<div class="panel-header">
|
|
87
|
+
<h3>Services</h3>
|
|
88
|
+
<div class="panel-actions">
|
|
89
|
+
<label><input type="checkbox" id="get-all-services" /> Get all</label>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
87
92
|
<div class="panel-body service-controls">
|
|
88
93
|
<input id="svc-filter" placeholder="filter service name..." />
|
|
89
94
|
</div>
|
|
@@ -147,9 +152,10 @@
|
|
|
147
152
|
</section>
|
|
148
153
|
</main>
|
|
149
154
|
|
|
150
|
-
<!-- TODO: Remove or provide context -->
|
|
151
155
|
<footer class="footer">
|
|
152
|
-
<div>
|
|
156
|
+
<div><code>PyObservability: v{{ version }}</code><br>
|
|
157
|
+
<a href="https://github.com/thevickypedia/PyObservability" target="_blank">https://github.com/thevickypedia/PyObservability</a>
|
|
158
|
+
</div>
|
|
153
159
|
</footer>
|
|
154
160
|
|
|
155
161
|
<script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fastapi import WebSocket, WebSocketDisconnect
|
|
6
|
+
|
|
7
|
+
from pyobservability.config import settings
|
|
8
|
+
from pyobservability.monitor import Monitor
|
|
9
|
+
|
|
10
|
+
LOGGER = logging.getLogger("uvicorn.default")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def _forward_metrics(websocket: WebSocket, q: asyncio.Queue):
|
|
14
|
+
while True:
|
|
15
|
+
payload = await q.get()
|
|
16
|
+
await websocket.send_json(payload)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
20
|
+
await websocket.accept()
|
|
21
|
+
|
|
22
|
+
monitor: Monitor | None = None
|
|
23
|
+
q: asyncio.Queue | None = None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
while True:
|
|
27
|
+
msg = await websocket.receive_text()
|
|
28
|
+
data = json.loads(msg)
|
|
29
|
+
if data.get("type") == "update_flags":
|
|
30
|
+
if monitor:
|
|
31
|
+
await monitor.update_flags(
|
|
32
|
+
all_services=data.get("all_services", False),
|
|
33
|
+
)
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
# -------------------------------------------
|
|
37
|
+
# UI requests a specific target to monitor
|
|
38
|
+
# -------------------------------------------
|
|
39
|
+
if data.get("type") == "select_target":
|
|
40
|
+
base_url = data["base_url"]
|
|
41
|
+
|
|
42
|
+
# stop old monitor
|
|
43
|
+
if monitor:
|
|
44
|
+
monitor.unsubscribe(q)
|
|
45
|
+
await monitor.stop()
|
|
46
|
+
|
|
47
|
+
if target := settings.targets_by_url.get(base_url):
|
|
48
|
+
LOGGER.info("Gathering metrics for: %s", target["name"])
|
|
49
|
+
else:
|
|
50
|
+
LOGGER.warning(f"Invalid base url: {base_url}")
|
|
51
|
+
raise WebSocketDisconnect(code=400, reason=f"Invalid base url: {base_url}")
|
|
52
|
+
|
|
53
|
+
# create new monitor
|
|
54
|
+
monitor = Monitor(target)
|
|
55
|
+
await monitor.start()
|
|
56
|
+
|
|
57
|
+
# new subscription queue
|
|
58
|
+
q = monitor.subscribe()
|
|
59
|
+
|
|
60
|
+
# start forwarding metrics
|
|
61
|
+
asyncio.create_task(_forward_metrics(websocket, q))
|
|
62
|
+
except WebSocketDisconnect:
|
|
63
|
+
pass
|
|
64
|
+
except Exception as err:
|
|
65
|
+
LOGGER.error("WS error: %s", err)
|
|
66
|
+
|
|
67
|
+
# cleanup
|
|
68
|
+
if monitor:
|
|
69
|
+
if q:
|
|
70
|
+
monitor.unsubscribe(q)
|
|
71
|
+
await monitor.stop()
|
pyobservability/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
|
|
2
|
+
pyobservability/main.py,sha256=Ty0bS7ZWyKXuo-xuKoFQESvT-B-19W04d_Nk0KveNtg,3004
|
|
3
|
+
pyobservability/monitor.py,sha256=4Xd8k7gcOmHM-WvQpgFiDVGtzMu_RpFPZXPPoz4GoA4,6224
|
|
4
|
+
pyobservability/transport.py,sha256=FyzJAMZPn7JUZIGgxnSw3on1K6T4ciZE1EuGdAcxt_w,2188
|
|
5
|
+
pyobservability/version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
6
|
+
pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
|
|
7
|
+
pyobservability/config/settings.py,sha256=BjTZnkGS1pZKbN3Fq_5HQX8C6oEjWn_gVJLXcZZ6EWE,4853
|
|
8
|
+
pyobservability/static/app.js,sha256=6hjFy2jt4ndYJUI1DZT08CpuxHyWj9iSAZ2vxaTqH2A,20664
|
|
9
|
+
pyobservability/static/styles.css,sha256=dnYSXNeXd6Ohu4h8sJCO87vzPbkYcw9XVGGwB8IJbxw,4703
|
|
10
|
+
pyobservability/templates/index.html,sha256=2aQdb0QlDY5Hboev-_lJPlpnGxiC6h3fp0FlvL72S9k,5227
|
|
11
|
+
pyobservability-1.0.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
12
|
+
pyobservability-1.0.0.dist-info/METADATA,sha256=4Wj2FH4KYhsDbEntXxnMginYE9QR9AluZyYLMHvwhbg,6822
|
|
13
|
+
pyobservability-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
pyobservability-1.0.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
15
|
+
pyobservability-1.0.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
16
|
+
pyobservability-1.0.0.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
|
|
2
|
-
pyobservability/main.py,sha256=HpKYxQ3XgyvxRP6giVOBIpBYTptZ-txL98KV5D5P8uI,3623
|
|
3
|
-
pyobservability/monitor.py,sha256=wu-CYsMCMQ1wZLTBaIyYLdu2FJvQ4rb41Lv2NEJ3yKM,4759
|
|
4
|
-
pyobservability/version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
5
|
-
pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
|
|
6
|
-
pyobservability/config/settings.py,sha256=HoDRuzwCCbCdNQLdOiy9JUoiM2BqUsRkX6zUyDLordY,3811
|
|
7
|
-
pyobservability/static/app.js,sha256=QQJ-1m7zwKQZz7nWTACS97mF79WGCXl5pNNQDWLmByM,17663
|
|
8
|
-
pyobservability/static/styles.css,sha256=ljK6m-q7GMZkhiYQ9bqqR1NbYu2pRXzlvNTol_PK9go,3521
|
|
9
|
-
pyobservability/templates/index.html,sha256=di7LKLVf3Ri0nxUn6dXVXlVPomQSRzZgWvNfTfxQb8s,5035
|
|
10
|
-
pyobservability-0.1.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
11
|
-
pyobservability-0.1.0.dist-info/METADATA,sha256=nDYtRE5m3gCHwO8LI7F0w1B1qy7sLLjHTg7hQfD8RLA,6822
|
|
12
|
-
pyobservability-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
pyobservability-0.1.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
14
|
-
pyobservability-0.1.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
15
|
-
pyobservability-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|