PyObservability 1.4.0__py3-none-any.whl → 1.4.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.
@@ -5,7 +5,7 @@
5
5
  // ------------------------------------------------------------
6
6
  const MAX_POINTS = 60;
7
7
  const targets = window.MONITOR_TARGETS || [];
8
- const DEFAULT_PAGE_SIZE = 15;
8
+ const DEFAULT_PAGE_SIZE = 10;
9
9
  const panelSpinners = {};
10
10
 
11
11
  // ------------------------------------------------------------
@@ -117,11 +117,26 @@
117
117
  const chunk = rows.slice(start, start + state.pageSize);
118
118
 
119
119
  info.textContent =
120
- `Showing ${start + 1} to ${Math.min(start + state.pageSize, rows.length)} of ${rows.length} entries`;
120
+ `Showing ${rows.length ? start + 1 : 0} to ${rows.length ? Math.min(start + state.pageSize, rows.length) : 0} of ${rows.length} entries`;
121
121
 
122
122
  bodyEl.innerHTML = "";
123
123
  chunk.forEach(r => bodyEl.insertAdjacentHTML("beforeend", r));
124
124
 
125
+ const fillerCount = Math.max(0, state.pageSize - chunk.length);
126
+ const shouldPad = state.page > 1 && fillerCount > 0;
127
+ if (shouldPad) {
128
+ const colCount = state.columns?.length || headEl.querySelectorAll("th").length || 1;
129
+ for (let i = 0; i < fillerCount; i++) {
130
+ const fillerRow = document.createElement("tr");
131
+ fillerRow.className = "placeholder-row";
132
+ for (let c = 0; c < colCount; c++) {
133
+ const cell = document.createElement("td");
134
+ cell.innerHTML = "&nbsp;";
135
+ fillerRow.appendChild(cell);
136
+ }
137
+ bodyEl.appendChild(fillerRow);
138
+ }
139
+ }
125
140
  renderPagination(pages);
126
141
  }
127
142
 
@@ -377,7 +392,53 @@
377
392
  const unifiedCpuCtx = document.getElementById("unified-cpu-chart").getContext("2d");
378
393
  const unifiedDiskCtx = document.getElementById("unified-disk-chart").getContext("2d");
379
394
 
395
+ // Unified tables DOM references
396
+ const unifiedServicesTable = document.getElementById("unified-services-table");
397
+ const unifiedServicesHead = unifiedServicesTable?.querySelector("thead");
398
+ const unifiedServicesBody = unifiedServicesTable?.querySelector("tbody");
399
+
400
+ const unifiedProcessesTable = document.getElementById("unified-processes-table");
401
+ const unifiedProcessesHead = unifiedProcessesTable?.querySelector("thead");
402
+ const unifiedProcessesBody = unifiedProcessesTable?.querySelector("tbody");
403
+
404
+ const unifiedDockerTable = document.getElementById("unified-docker-table");
405
+ const unifiedDockerHead = unifiedDockerTable?.querySelector("thead");
406
+ const unifiedDockerBody = unifiedDockerTable?.querySelector("tbody");
407
+
408
+ const unifiedDisksTable = document.getElementById("unified-disks-table");
409
+ const unifiedDisksHead = unifiedDisksTable?.querySelector("thead");
410
+ const unifiedDisksBody = unifiedDisksTable?.querySelector("tbody");
411
+
412
+ const unifiedPyudiskTable = document.getElementById("unified-pyudisk-table");
413
+ const unifiedPyudiskHead = unifiedPyudiskTable?.querySelector("thead");
414
+ const unifiedPyudiskBody = unifiedPyudiskTable?.querySelector("tbody");
415
+
416
+ const unifiedCertsTable = document.getElementById("unified-certificates-table");
417
+ const unifiedCertsHead = unifiedCertsTable?.querySelector("thead");
418
+ const unifiedCertsBody = unifiedCertsTable?.querySelector("tbody");
419
+
420
+ // Paginated unified tables
421
+ const PAG_UNIFIED_SERVICES = unifiedServicesTable && createPaginatedTable(
422
+ unifiedServicesTable, unifiedServicesHead, unifiedServicesBody
423
+ );
424
+ const PAG_UNIFIED_PROCESSES = unifiedProcessesTable && createPaginatedTable(
425
+ unifiedProcessesTable, unifiedProcessesHead, unifiedProcessesBody
426
+ );
427
+ const PAG_UNIFIED_DOCKER = unifiedDockerTable && createPaginatedTable(
428
+ unifiedDockerTable, unifiedDockerHead, unifiedDockerBody
429
+ );
430
+ const PAG_UNIFIED_DISKS = unifiedDisksTable && createPaginatedTable(
431
+ unifiedDisksTable, unifiedDisksHead, unifiedDisksBody
432
+ );
433
+ const PAG_UNIFIED_PYUDISK = unifiedPyudiskTable && createPaginatedTable(
434
+ unifiedPyudiskTable, unifiedPyudiskHead, unifiedPyudiskBody
435
+ );
436
+ const PAG_UNIFIED_CERTS = unifiedCertsTable && createPaginatedTable(
437
+ unifiedCertsTable, unifiedCertsHead, unifiedCertsBody
438
+ );
439
+
380
440
  let unifiedNodes = [];
441
+ // TODO: Update colorPalette to use contrasting colors
381
442
  const colorPalette = ["#63b3ff", "#ff99c8", "#7dd3fc", "#fbbf24", "#a3e635", "#f87171", "#c084fc", "#38bdf8"];
382
443
  const nodeColor = {};
383
444
  const unifiedCharts = {memory: null, cpu: null, disk: null};
@@ -511,6 +572,147 @@
511
572
 
512
573
  chart.update("none");
513
574
  });
575
+
576
+ // --- Unified tables aggregation ---
577
+ // Helper to get display name for node
578
+ const getNodeLabel = (host) => host.name || host.base_url || "";
579
+
580
+ // Services
581
+ if (PAG_UNIFIED_SERVICES) {
582
+ const svcRows = [];
583
+ metrics.forEach(host => {
584
+ if (!host.metrics) return;
585
+ const m = host.metrics;
586
+ const label = getNodeLabel(host);
587
+ const services = (m.service_stats || m.services || []).filter(s =>
588
+ (s.pname || s.Name || "").toLowerCase().includes(
589
+ svcFilter.value.trim().toLowerCase()
590
+ )
591
+ );
592
+ services.forEach(s => {
593
+ svcRows.push({
594
+ Node: label,
595
+ PID: s.PID ?? s.pid ?? "",
596
+ Name: s.pname ?? s.Name ?? s.name ?? "",
597
+ Status: s.Status ?? s.active ?? s.status ?? s.Active ?? "4",
598
+ CPU: objectToString(s.CPU, s.cpu),
599
+ Memory: objectToString(s.Memory, s.memory),
600
+ Threads: s.Threads ?? s.threads ?? "4",
601
+ "Open Files": s["Open Files"] ?? s.open_files ?? "4"
602
+ });
603
+ });
604
+ });
605
+ const svcCols = ["Node", "PID", "Name", "Status", "CPU", "Memory", "Threads", "Open Files"];
606
+ PAG_UNIFIED_SERVICES.setData(svcRows, svcCols);
607
+ }
608
+
609
+ // Processes
610
+ if (PAG_UNIFIED_PROCESSES) {
611
+ const procRows = [];
612
+ const procColsSet = new Set(["Node", "PID", "Name", "Status", "CPU", "Memory", "Uptime", "Threads", "Open Files"]);
613
+ metrics.forEach(host => {
614
+ if (!host.metrics) return;
615
+ const m = host.metrics;
616
+ const label = getNodeLabel(host);
617
+ const processes = (m.process_stats || []).filter(p =>
618
+ (p.Name || "").toLowerCase().includes(
619
+ procFilter.value.trim().toLowerCase()
620
+ )
621
+ );
622
+ processes.forEach(p => {
623
+ const row = {Node: label};
624
+ Object.entries(p).forEach(([k, v]) => {
625
+ procColsSet.add(k);
626
+ row[k] = v;
627
+ });
628
+ procRows.push(row);
629
+ });
630
+ });
631
+ const procCols = Array.from(procColsSet);
632
+ PAG_UNIFIED_PROCESSES.setData(procRows, procCols);
633
+ }
634
+
635
+ // Docker
636
+ if (PAG_UNIFIED_DOCKER) {
637
+ const dockerRows = [];
638
+ const dockerColsSet = new Set(["Node"]);
639
+ metrics.forEach(host => {
640
+ if (!host.metrics || !Array.isArray(host.metrics.docker_stats)) return;
641
+ const label = getNodeLabel(host);
642
+ host.metrics.docker_stats.forEach(s => {
643
+ const row = {Node: label};
644
+ Object.entries(s).forEach(([k, v]) => {
645
+ dockerColsSet.add(k);
646
+ row[k] = v;
647
+ });
648
+ dockerRows.push(row);
649
+ });
650
+ });
651
+ const dockerCols = Array.from(dockerColsSet);
652
+ PAG_UNIFIED_DOCKER.setData(dockerRows, dockerCols);
653
+ }
654
+
655
+ // Disks
656
+ if (PAG_UNIFIED_DISKS) {
657
+ const diskRows = [];
658
+ const diskColsSet = new Set(["Node"]);
659
+ metrics.forEach(host => {
660
+ if (!host.metrics || !Array.isArray(host.metrics.disks_info)) return;
661
+ const label = getNodeLabel(host);
662
+ host.metrics.disks_info.forEach(d => {
663
+ const row = {Node: label};
664
+ Object.entries(d).forEach(([k, v]) => {
665
+ if (k === "Node") return;
666
+ diskColsSet.add(k);
667
+ row[k] = v;
668
+ });
669
+ diskRows.push(row);
670
+ });
671
+ });
672
+ const diskCols = Array.from(diskColsSet);
673
+ PAG_UNIFIED_DISKS.setData(diskRows, diskCols);
674
+ }
675
+
676
+ // PyUdisk
677
+ if (PAG_UNIFIED_PYUDISK) {
678
+ const pyuRows = [];
679
+ const pyuColsSet = new Set(["Node"]);
680
+ metrics.forEach(host => {
681
+ if (!host.metrics || !Array.isArray(host.metrics.pyudisk_stats)) return;
682
+ const label = getNodeLabel(host);
683
+ host.metrics.pyudisk_stats.forEach(pyu => {
684
+ const row = {Node: label};
685
+ Object.entries(pyu).forEach(([k, v]) => {
686
+ if (k === "Mountpoint") return;
687
+ pyuColsSet.add(k);
688
+ row[k] = v;
689
+ });
690
+ pyuRows.push(row);
691
+ });
692
+ });
693
+ const pyuCols = Array.from(pyuColsSet);
694
+ PAG_UNIFIED_PYUDISK.setData(pyuRows, pyuCols);
695
+ }
696
+
697
+ // Certificates
698
+ if (PAG_UNIFIED_CERTS) {
699
+ const certRows = [];
700
+ const certColsSet = new Set(["Node"]);
701
+ metrics.forEach(host => {
702
+ if (!host.metrics || !Array.isArray(host.metrics.certificates)) return;
703
+ const label = getNodeLabel(host);
704
+ host.metrics.certificates.forEach(c => {
705
+ const row = {Node: label};
706
+ Object.entries(c).forEach(([k, v]) => {
707
+ certColsSet.add(k);
708
+ row[k] = v;
709
+ });
710
+ certRows.push(row);
711
+ });
712
+ });
713
+ const certCols = Array.from(certColsSet);
714
+ PAG_UNIFIED_CERTS.setData(certRows, certCols);
715
+ }
514
716
  }
515
717
 
516
718
  // ------------------------------------------------------------
@@ -565,6 +767,12 @@
565
767
  PAG_DISKS.setData([], []);
566
768
  PAG_PYUDISK.setData([], []);
567
769
  PAG_CERTS.setData([], []);
770
+ if (PAG_UNIFIED_SERVICES) PAG_UNIFIED_SERVICES.setData([], []);
771
+ if (PAG_UNIFIED_PROCESSES) PAG_UNIFIED_PROCESSES.setData([], []);
772
+ if (PAG_UNIFIED_DOCKER) PAG_UNIFIED_DOCKER.setData([], []);
773
+ if (PAG_UNIFIED_DISKS) PAG_UNIFIED_DISKS.setData([], []);
774
+ if (PAG_UNIFIED_PYUDISK) PAG_UNIFIED_PYUDISK.setData([], []);
775
+ if (PAG_UNIFIED_CERTS) PAG_UNIFIED_CERTS.setData([], []);
568
776
  }
569
777
 
570
778
  function resetUI() {
@@ -825,20 +1033,15 @@
825
1033
  }
826
1034
  }
827
1035
 
828
- if (selectedBase === "*") {
829
- if (ensureUnifiedChart(list)) {
830
- updateUnified(list);
1036
+ // When not in unified ("*") mode, ensure unified panel is hidden and charts cleared
1037
+ unifiedPanel.classList.add("hidden");
1038
+ unifiedNodes = [];
1039
+ Object.keys(unifiedCharts).forEach(key => {
1040
+ if (unifiedCharts[key]) {
1041
+ unifiedCharts[key].destroy();
1042
+ unifiedCharts[key] = null;
831
1043
  }
832
- } else {
833
- unifiedPanel.classList.add("hidden");
834
- unifiedNodes = [];
835
- Object.keys(unifiedCharts).forEach(key => {
836
- if (unifiedCharts[key]) {
837
- unifiedCharts[key].destroy();
838
- unifiedCharts[key] = null;
839
- }
840
- });
841
- }
1044
+ });
842
1045
  }
843
1046
 
844
1047
  // ------------------------------------------------------------
@@ -201,6 +201,16 @@ html, body {
201
201
  text-align: left;
202
202
  }
203
203
 
204
+ .table tbody tr.placeholder-row td {
205
+ color: transparent;
206
+ border-bottom: 1px solid transparent;
207
+ padding: 8px;
208
+ }
209
+
210
+ .table tbody tr.placeholder-row td::after {
211
+ content: "";
212
+ }
213
+
204
214
  .pre {
205
215
  background: rgba(255, 255, 255, 0.02);
206
216
  padding: 8px;
@@ -425,6 +435,32 @@ input#proc-filter {
425
435
  font-size: 13px;
426
436
  }
427
437
 
438
+ .unified-tables {
439
+ margin-top: 12px;
440
+ }
441
+
442
+ .unified-tables-title {
443
+ margin: 0 0 8px;
444
+ font-size: 13px;
445
+ color: var(--muted);
446
+ }
447
+
448
+ .unified-tables-grid {
449
+ display: flex;
450
+ flex-direction: column;
451
+ gap: 12px;
452
+ }
453
+
454
+ .unified-table-panel {
455
+ display: flex;
456
+ flex-direction: column;
457
+ gap: 8px;
458
+ }
459
+
460
+ .unified-table-panel .table {
461
+ font-size: 12px;
462
+ }
463
+
428
464
  body.unified-mode .meta-row,
429
465
  body.unified-mode .charts-row > :not(#unified-panel),
430
466
  body.unified-mode .details-row,
@@ -98,7 +98,54 @@
98
98
  <canvas id="unified-disk-chart" class="chart"></canvas>
99
99
  </div>
100
100
  </div>
101
+ {# TODO: Make the following tables into multiple div containers #}
101
102
  <div id="unified-legend" class="unified-legend"></div>
103
+ <div class="unified-tables">
104
+ <div class="unified-tables-grid">
105
+ <div class="panel unified-table-panel">
106
+ <div class="panel-header"><h3>Services</h3></div>
107
+ <table class="table" id="unified-services-table">
108
+ <thead></thead>
109
+ <tbody></tbody>
110
+ </table>
111
+ </div>
112
+ <div class="panel unified-table-panel">
113
+ <div class="panel-header"><h3>Processes</h3></div>
114
+ <table class="table" id="unified-processes-table">
115
+ <thead></thead>
116
+ <tbody></tbody>
117
+ </table>
118
+ </div>
119
+ <div class="panel unified-table-panel">
120
+ <div class="panel-header"><h3>Docker Containers</h3></div>
121
+ <table class="table" id="unified-docker-table">
122
+ <thead></thead>
123
+ <tbody></tbody>
124
+ </table>
125
+ </div>
126
+ <div class="panel unified-table-panel">
127
+ <div class="panel-header"><h3>Disks</h3></div>
128
+ <table class="table" id="unified-disks-table">
129
+ <thead></thead>
130
+ <tbody></tbody>
131
+ </table>
132
+ </div>
133
+ <div class="panel unified-table-panel">
134
+ <div class="panel-header"><h3>PyUdisk Metrics</h3></div>
135
+ <table class="table" id="unified-pyudisk-table">
136
+ <thead></thead>
137
+ <tbody></tbody>
138
+ </table>
139
+ </div>
140
+ <div class="panel unified-table-panel">
141
+ <div class="panel-header"><h3>Certificates</h3></div>
142
+ <table class="table" id="unified-certificates-table">
143
+ <thead></thead>
144
+ <tbody></tbody>
145
+ </table>
146
+ </div>
147
+ </div>
148
+ </div>
102
149
  </div>
103
150
  </section>
104
151
 
@@ -1 +1 @@
1
- __version__ = "1.4.0"
1
+ __version__ = "1.4.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyObservability
3
- Version: 1.4.0
3
+ Version: 1.4.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
@@ -0,0 +1,16 @@
1
+ pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
2
+ pyobservability/main.py,sha256=EJ49ENDnKy07jKHQ0IYEVmsyc_4O3uxCHpn1faQD5VM,4534
3
+ pyobservability/monitor.py,sha256=i_Xf_DB-qLOp1b9wryekjwHIM8AnMrGTkuEg7e08bcM,7539
4
+ pyobservability/transport.py,sha256=S-84mgf-9yMj0H7VSAmueW9yosX_1XxdyNJC2EuQHQQ,8493
5
+ pyobservability/version.py,sha256=BqOI5y46o1G1RWC9bF1DPL-YM68lGYPmZt1pn6FZFZs,22
6
+ pyobservability/config/enums.py,sha256=EhvD9kB5EMW3ARxr5KmISmf-rP3D4IKqOIjw6Tb8SB8,294
7
+ pyobservability/config/settings.py,sha256=ylhiT0SARHuzkyon_1otgsO74AfA6aiUKp5uczZQj08,5980
8
+ pyobservability/static/app.js,sha256=l6m8mwEU4z5d2wQF7Fa6tFITW3HX3M5RaLhBJ0jFXrM,43322
9
+ pyobservability/static/styles.css,sha256=DRJ4kw-LDlXMcQXxFd8cEDuDC_ZfwZgARAjn0zDWwRk,8172
10
+ pyobservability/templates/index.html,sha256=Z_r1Gq0QNxEwTL4_2NPQ1cKqLpbQoJC88E5leyjo07s,10786
11
+ pyobservability-1.4.1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
12
+ pyobservability-1.4.1.dist-info/METADATA,sha256=QUIDftx5D229DYD03NsspRYP2Imp0u_rVVlzW4-UPHo,6537
13
+ pyobservability-1.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
+ pyobservability-1.4.1.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
15
+ pyobservability-1.4.1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
16
+ pyobservability-1.4.1.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
2
- pyobservability/main.py,sha256=EJ49ENDnKy07jKHQ0IYEVmsyc_4O3uxCHpn1faQD5VM,4534
3
- pyobservability/monitor.py,sha256=i_Xf_DB-qLOp1b9wryekjwHIM8AnMrGTkuEg7e08bcM,7539
4
- pyobservability/transport.py,sha256=S-84mgf-9yMj0H7VSAmueW9yosX_1XxdyNJC2EuQHQQ,8493
5
- pyobservability/version.py,sha256=8UhoYEXHs1Oai7BW_ExBmuwWnRI-yMG_u1fQAXMizHQ,22
6
- pyobservability/config/enums.py,sha256=EhvD9kB5EMW3ARxr5KmISmf-rP3D4IKqOIjw6Tb8SB8,294
7
- pyobservability/config/settings.py,sha256=ylhiT0SARHuzkyon_1otgsO74AfA6aiUKp5uczZQj08,5980
8
- pyobservability/static/app.js,sha256=lel2ZZH-ifjehzy2d-5UQxOhnBIHxla05xjokW1nXXA,33919
9
- pyobservability/static/styles.css,sha256=0Vagj7nDac27JC0M870V3yqc1XN4rB5pDYuy4zjit3c,7618
10
- pyobservability/templates/index.html,sha256=JdGn7Bg9w-4zzcMzX78p0q5760ao7sioaqB1s_8d0Fs,8421
11
- pyobservability-1.4.0.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
12
- pyobservability-1.4.0.dist-info/METADATA,sha256=DHbIRiPU0Vi_5PjsT0k5NgMInO8_YgY5azP9fGQM8VQ,6537
13
- pyobservability-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- pyobservability-1.4.0.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
15
- pyobservability-1.4.0.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
16
- pyobservability-1.4.0.dist-info/RECORD,,