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.
@@ -7,8 +7,12 @@
7
7
  }
8
8
 
9
9
  * { box-sizing: border-box; }
10
- html,body { height:100%; margin:0; font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; background:var(--bg); color:#e6eef8; }
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 { background:var(--panel); color:inherit; border:1px solid rgba(255,255,255,0.04); padding:6px 8px; border-radius:6px; }
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:flex; gap:12px; }
25
- .meta-card { background:var(--panel); padding:12px; border-radius:8px; width:1fr; min-width:120px; flex:1; box-shadow:0 4px 12px rgba(2,6,23,0.6); }
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 { background:rgba(255,255,255,0.02); padding:8px; border-radius:6px; overflow:auto; white-space:pre-wrap; font-family:monospace; font-size:13px; }
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">—</div>
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="gpu" class="meta-value pre">—</div>
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">—</div>
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">—</div>
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">—</div>
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"><h3>Services</h3></div>
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><tr><th>Name</th><th>Size</th><th>Mounts</th></tr></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
- <pre id="certificates" class="pre">—</pre>
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>OpenAPI: <code>/static/openapi.json</code> (uploaded file available at <code>/mnt/data/openapi.json</code>)</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()
@@ -1 +1 @@
1
- __version__ = "0.0.3"
1
+ __version__ = "1.0.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyObservability
3
- Version: 0.0.3
3
+ Version: 1.0.0
4
4
  Summary: Lightweight OS-agnostic observability UI for PyNinja
5
5
  Author-email: Vignesh Rao <svignesh1793@gmail.com>
6
6
  License: MIT License
@@ -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,,