PyObservability 0.0.3__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 +12 -32
- pyobservability/monitor.py +146 -168
- pyobservability/static/app.js +366 -185
- pyobservability/static/styles.css +62 -6
- pyobservability/templates/index.html +48 -11
- pyobservability/transport.py +71 -0
- pyobservability/version.py +1 -1
- {pyobservability-0.0.3.dist-info → pyobservability-1.0.0.dist-info}/METADATA +1 -1
- pyobservability-1.0.0.dist-info/RECORD +16 -0
- pyobservability-0.0.3.dist-info/RECORD +0 -15
- {pyobservability-0.0.3.dist-info → pyobservability-1.0.0.dist-info}/WHEEL +0 -0
- {pyobservability-0.0.3.dist-info → pyobservability-1.0.0.dist-info}/entry_points.txt +0 -0
- {pyobservability-0.0.3.dist-info → pyobservability-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {pyobservability-0.0.3.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,15 +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:
|
|
25
|
-
.meta-card {
|
|
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
|
+
}
|
|
26
35
|
.meta-title { font-size:12px; color:var(--muted); }
|
|
27
|
-
.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; }
|
|
28
37
|
.meta-value.pre { white-space:pre-wrap; font-family:monospace; font-size:13px; }
|
|
29
38
|
|
|
30
39
|
.charts-row { display:flex; gap:12px; }
|
|
@@ -47,14 +56,61 @@ html,body { height:100%; margin:0; font-family: Inter, system-ui, -apple-system,
|
|
|
47
56
|
.tables-row { display:grid; grid-template-columns:1fr; grid-auto-rows:auto; gap:12px; }
|
|
48
57
|
.table { width:100%; border-collapse:collapse; font-size:13px; }
|
|
49
58
|
.table th, .table td { padding:8px; border-bottom:1px solid rgba(255,255,255,0.03); text-align:left; }
|
|
50
|
-
.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
|
+
}
|
|
51
62
|
.list { padding-left:14px; margin:8px 0; }
|
|
52
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; }
|
|
53
64
|
.service-controls { display:flex; gap:8px; padding-bottom:8px; }
|
|
54
65
|
input#svc-filter { flex:1; padding:6px 8px; background:var(--panel); border:1px solid rgba(255,255,255,0.04); color:inherit; border-radius:6px; }
|
|
66
|
+
input#proc-filter { flex:1; padding:6px 8px; background:var(--panel); border:1px solid rgba(255,255,255,0.04); color:inherit; border-radius:6px; }
|
|
55
67
|
|
|
56
68
|
@media (max-width:900px) {
|
|
57
69
|
.tables-row { grid-template-columns: 1fr; }
|
|
58
70
|
.charts-row { flex-direction:column; }
|
|
59
71
|
.details-row { flex-direction:column; }
|
|
72
|
+
.meta-row { grid-template-columns: 1fr; }
|
|
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;
|
|
60
116
|
}
|
|
@@ -21,29 +21,34 @@
|
|
|
21
21
|
|
|
22
22
|
<main class="container">
|
|
23
23
|
<section class="meta-row">
|
|
24
|
+
<div class="meta-card">
|
|
25
|
+
<div class="meta-title">System</div>
|
|
26
|
+
<div id="system" class="meta-value pre"></div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
24
29
|
<div class="meta-card">
|
|
25
30
|
<div class="meta-title">IP</div>
|
|
26
|
-
<div id="ip" class="meta-value"
|
|
31
|
+
<div id="ip-info" class="meta-value pre"></div>
|
|
27
32
|
</div>
|
|
28
33
|
|
|
29
34
|
<div class="meta-card">
|
|
30
35
|
<div class="meta-title">GPU / CPU Model</div>
|
|
31
|
-
<div id="
|
|
36
|
+
<div id="processor" class="meta-value pre"></div>
|
|
32
37
|
</div>
|
|
33
38
|
|
|
34
39
|
<div class="meta-card">
|
|
35
40
|
<div class="meta-title">CPU Load (1/5/15)</div>
|
|
36
|
-
<div id="cpuload" class="meta-value"
|
|
41
|
+
<div id="cpuload" class="meta-value pre"></div>
|
|
37
42
|
</div>
|
|
38
43
|
|
|
39
44
|
<div class="meta-card">
|
|
40
45
|
<div class="meta-title">Disk ( / )</div>
|
|
41
|
-
<div id="disk" class="meta-value pre"
|
|
46
|
+
<div id="disk" class="meta-value pre"></div>
|
|
42
47
|
</div>
|
|
43
48
|
|
|
44
49
|
<div class="meta-card">
|
|
45
50
|
<div class="meta-title">Memory</div>
|
|
46
|
-
<div id="memory" class="meta-value pre"
|
|
51
|
+
<div id="memory" class="meta-value pre"></div>
|
|
47
52
|
</div>
|
|
48
53
|
</section>
|
|
49
54
|
|
|
@@ -52,7 +57,7 @@
|
|
|
52
57
|
<div class="panel-header">
|
|
53
58
|
<h3>CPU (Average)</h3>
|
|
54
59
|
<div class="panel-actions">
|
|
55
|
-
<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>
|
|
56
61
|
</div>
|
|
57
62
|
</div>
|
|
58
63
|
<canvas id="cpu-avg-chart" class="chart"></canvas>
|
|
@@ -78,12 +83,29 @@
|
|
|
78
83
|
|
|
79
84
|
<section class="tables-row">
|
|
80
85
|
<div class="panel">
|
|
81
|
-
<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>
|
|
82
92
|
<div class="panel-body service-controls">
|
|
83
93
|
<input id="svc-filter" placeholder="filter service name..." />
|
|
84
94
|
</div>
|
|
85
95
|
<table class="table" id="services-table">
|
|
86
|
-
<thead><tr><th>PID</th><th>Name</th><th>Status</th><th>CPU</th><th>Memory</th></tr></thead>
|
|
96
|
+
<thead><tr><th>PID</th><th>Name</th><th>Status</th><th>CPU</th><th>Memory</th><th>Threads</th><th>Open Files</th></tr></thead>
|
|
97
|
+
<tbody></tbody>
|
|
98
|
+
</table>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<section class="tables-row">
|
|
102
|
+
<div class="panel">
|
|
103
|
+
<div class="panel-header"><h3>Processes</h3></div>
|
|
104
|
+
<div class="panel-body service-controls">
|
|
105
|
+
<input id="proc-filter" placeholder="filter process name..." />
|
|
106
|
+
</div>
|
|
107
|
+
<table class="table" id="processes-table">
|
|
108
|
+
<thead><tr><th>PID</th><th>Name</th><th>Status</th><th>CPU</th><th>Memory</th><th>Uptime</th><th>Threads</th><th>Open Files</th></tr></thead>
|
|
87
109
|
<tbody></tbody>
|
|
88
110
|
</table>
|
|
89
111
|
</div>
|
|
@@ -102,7 +124,17 @@
|
|
|
102
124
|
<div class="panel-header"><h3>Disks</h3></div>
|
|
103
125
|
<div class="panel-body">
|
|
104
126
|
<table class="table" id="disks-table">
|
|
105
|
-
<thead
|
|
127
|
+
<thead></thead>
|
|
128
|
+
<tbody></tbody>
|
|
129
|
+
</table>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
<div class="panel">
|
|
134
|
+
<div class="panel-header"><h3>PyUdisk</h3></div>
|
|
135
|
+
<div class="panel-body">
|
|
136
|
+
<table class="table" id="pyudisk-table">
|
|
137
|
+
<thead></thead>
|
|
106
138
|
<tbody></tbody>
|
|
107
139
|
</table>
|
|
108
140
|
</div>
|
|
@@ -111,14 +143,19 @@
|
|
|
111
143
|
<div class="panel">
|
|
112
144
|
<div class="panel-header"><h3>Certificates</h3></div>
|
|
113
145
|
<div class="panel-body">
|
|
114
|
-
<
|
|
146
|
+
<table class="table" id="certificates-table">
|
|
147
|
+
<thead></thead>
|
|
148
|
+
<tbody></tbody>
|
|
149
|
+
</table>
|
|
115
150
|
</div>
|
|
116
151
|
</div>
|
|
117
152
|
</section>
|
|
118
153
|
</main>
|
|
119
154
|
|
|
120
155
|
<footer class="footer">
|
|
121
|
-
<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>
|
|
122
159
|
</footer>
|
|
123
160
|
|
|
124
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__ = "0.0
|
|
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=QCoALD_vk3S5XKEIwMI_LzfNixzq7xpIS0uA21uMqs0,3414
|
|
3
|
-
pyobservability/monitor.py,sha256=bDbkSOHgtS_9Jv7IqZ9eN1P_-cFeZFlnozRo2N6hh_s,6578
|
|
4
|
-
pyobservability/version.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
|
|
5
|
-
pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
|
|
6
|
-
pyobservability/config/settings.py,sha256=HoDRuzwCCbCdNQLdOiy9JUoiM2BqUsRkX6zUyDLordY,3811
|
|
7
|
-
pyobservability/static/app.js,sha256=nXUWAoRDUTKAtRfLmg6GveBQlRtNk-UoVp09AXvSlrA,13743
|
|
8
|
-
pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
|
|
9
|
-
pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
|
|
10
|
-
pyobservability-0.0.3.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
|
|
11
|
-
pyobservability-0.0.3.dist-info/METADATA,sha256=mdFfoTsAoeh2HlXq6ndwjzwVVM17xT8IwQh1NQEr1QQ,6822
|
|
12
|
-
pyobservability-0.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
pyobservability-0.0.3.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
|
|
14
|
-
pyobservability-0.0.3.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
|
|
15
|
-
pyobservability-0.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|