PyObservability 5.0.2__py3-none-any.whl → 5.0.4__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.
@@ -3,7 +3,7 @@ import logging
3
3
  import os
4
4
  import pathlib
5
5
  import socket
6
- from typing import Any, Dict, Iterable, List
6
+ from typing import Any, Dict, Iterable, List, Optional
7
7
 
8
8
  from pydantic import BaseModel, Field, FilePath, HttpUrl, PositiveInt
9
9
  from pydantic.aliases import AliasChoices
@@ -67,6 +67,80 @@ def detailed_log_config(filename: str | None = None, debug: bool = False) -> Dic
67
67
  }
68
68
 
69
69
 
70
+ class TagModel(BaseModel):
71
+ """Represents a tag associated with a monitor.
72
+
73
+ >>> TagModel
74
+
75
+ """
76
+
77
+ id: int
78
+ monitor_id: int
79
+ tag_id: int
80
+ value: str
81
+ name: str
82
+ color: str
83
+
84
+
85
+ class KumaConfig(BaseModel):
86
+ """Configuration settings for Uptime Kuma monitors.
87
+
88
+ >>> KumaConfig
89
+
90
+ """
91
+
92
+ id: int
93
+ name: str
94
+ description: Optional[str]
95
+ pathName: str
96
+ childrenIDs: List[int]
97
+ url: str
98
+ method: str
99
+ maxretries: int
100
+ weight: int
101
+ active: bool
102
+ forceInactive: bool
103
+ type: str
104
+ timeout: int
105
+ interval: int
106
+ retryInterval: int
107
+ resendInterval: int
108
+ invertKeyword: bool
109
+ expiryNotification: bool
110
+ ignoreTls: bool
111
+ upsideDown: bool
112
+ packetSize: int
113
+ maxredirects: int
114
+ accepted_statuscodes: List[str]
115
+ dns_resolve_type: str
116
+ dns_resolve_server: str
117
+ docker_container: str
118
+ notificationIDList: Dict[str, bool]
119
+ tags: List[TagModel]
120
+ maintenance: bool
121
+ mqttTopic: str
122
+ mqttSuccessMessage: str
123
+ grpcEnableTls: bool
124
+ gamedigGivenPortOnly: bool
125
+ httpBodyEncoding: str
126
+ jsonPath: Optional[str]
127
+ kafkaProducerBrokers: List[str]
128
+ kafkaProducerSsl: bool
129
+ kafkaProducerAllowAutoTopicCreation: bool
130
+ oauth_auth_method: str
131
+ mqttUsername: str
132
+ mqttPassword: str
133
+ kafkaProducerSaslOptions: Dict[str, str]
134
+ includeSensitiveData: bool
135
+ parent: Optional[int]
136
+ authMethod: Optional[str]
137
+ body: Optional[str]
138
+ basic_auth_user: Optional[str]
139
+ basic_auth_pass: Optional[str]
140
+ authWorkstation: Optional[str]
141
+ authDomain: Optional[str]
142
+
143
+
70
144
  class PydanticEnvConfig(BaseSettings):
71
145
  """Pydantic BaseSettings with custom order for loading environment variables.
72
146
 
pyobservability/github.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  from collections.abc import Generator
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, fields
4
4
  from typing import Any, Dict, List
5
5
 
6
6
  import requests
@@ -52,12 +52,19 @@ class Runner:
52
52
 
53
53
  """
54
54
 
55
- id: int
56
- name: str
57
- os: str
58
- status: str
59
- busy: bool
60
- labels: List[str]
55
+ id: int = None
56
+ name: str = None
57
+ os: str = None
58
+ status: str = None
59
+ busy: bool = None
60
+ labels: List[str] = None
61
+ version: str = None
62
+
63
+ def __post_init__(self):
64
+ """Add 'v' prefix only if it doesn't already exist in the version string."""
65
+ if self.version is not None:
66
+ if not self.version.startswith("v"):
67
+ self.version = f"v{self.version}"
61
68
 
62
69
 
63
70
  @dataclass
@@ -101,7 +108,12 @@ class GitHub:
101
108
  (label["name"] for label in runner["labels"] if label["name"] != "self-hosted"),
102
109
  key=lambda s: s.lower(),
103
110
  )
104
- yield Runner(**{**runner, **{"labels": labels}})
111
+ kwargs = {**runner, **{"labels": labels}}
112
+ field_names = [f.name for f in fields(Runner)]
113
+ if missing := set(field_names) - set(kwargs.keys()):
114
+ LOGGER.warning("Missing field(s) in runner information: %s", missing)
115
+ kwargs = {k: v for k, v in kwargs.items() if k in field_names}
116
+ yield Runner(**kwargs)
105
117
 
106
118
  def runners(self) -> Runners | None:
107
119
  """Fetches the runners information from the GitHub API.
pyobservability/kuma.py CHANGED
@@ -137,22 +137,25 @@ def extract_monitors(payload: Dict[int, Dict[str, Any]]) -> Generator[Dict[str,
137
137
  if current_host in replacements:
138
138
  current_host = ip_address() or current_host
139
139
 
140
- for monitor in payload.values():
141
- url = monitor.get("url")
142
- host = urlparse(url).hostname if url else None
140
+ for monitor_ in payload.values():
141
+ monitor = settings.KumaConfig(**monitor_)
142
+ if not monitor.active:
143
+ LOGGER.warning("Monitor %s is disabled", monitor.name)
144
+ continue
145
+ host = urlparse(monitor.url).hostname if monitor.url else None
143
146
  if not host:
144
147
  continue
145
148
  # If any monitor has localhost, replace it with kuma host
146
149
  if host in replacements:
147
150
  # 1. Replace the host in the URL with the current host
148
- url = url.replace(host, current_host)
151
+ monitor.url = monitor.url.replace(host, current_host)
149
152
  # 2. Update the host variable to reflect the new host
150
153
  host = current_host
151
154
  yield {
152
- "name": monitor.get("name"),
153
- "parent": grouped.get(monitor.get("id")),
154
- "description": monitor.get("description"),
155
- "url": url,
155
+ "name": monitor.name,
156
+ "parent": grouped.get(monitor.id),
157
+ "description": monitor.description,
158
+ "url": monitor.url,
156
159
  "host": host,
157
- "tags": [tag.get("name") for tag in monitor.get("tags", []) if "name" in tag],
160
+ "tags": [tag.name for tag in monitor.tags if "name" in tag],
158
161
  }
@@ -1427,7 +1427,7 @@
1427
1427
  if (!PAG_RUNNERS_TAB) return; // Runners tab not enabled
1428
1428
 
1429
1429
  if (runnersDataLoaded) {
1430
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1430
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1431
1431
  return;
1432
1432
  }
1433
1433
 
@@ -1449,10 +1449,11 @@
1449
1449
  OS: runner.os || "—",
1450
1450
  Status: runner.status || "—",
1451
1451
  Busy: runner.busy ? "Yes" : "No",
1452
- Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—")
1452
+ Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—"),
1453
+ Version: runner.version || "—"
1453
1454
  }));
1454
1455
 
1455
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1456
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1456
1457
  } catch (err) {
1457
1458
  console.error("Error loading runners data:", err);
1458
1459
  runnersMainThead.innerHTML = '<tr><th>Error</th></tr>';
@@ -1474,13 +1475,14 @@
1474
1475
  OS: runner.os || "—",
1475
1476
  Status: runner.status || "—",
1476
1477
  Busy: runner.busy ? "Yes" : "No",
1477
- Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—")
1478
+ Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—"),
1479
+ Version: runner.version || "—"
1478
1480
  }));
1479
1481
 
1480
1482
  // Reapply current search filter if any
1481
1483
  const searchTerm = runnersSearchInput?.value?.toLowerCase() || '';
1482
1484
  if (!searchTerm) {
1483
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1485
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1484
1486
  } else {
1485
1487
  const filtered = allRunnersRows.filter(row =>
1486
1488
  row.ID.toString().toLowerCase().includes(searchTerm) ||
@@ -1488,9 +1490,10 @@
1488
1490
  row.OS.toLowerCase().includes(searchTerm) ||
1489
1491
  row.Status.toLowerCase().includes(searchTerm) ||
1490
1492
  row.Busy.toLowerCase().includes(searchTerm) ||
1491
- row.Labels.toLowerCase().includes(searchTerm)
1493
+ row.Labels.toLowerCase().includes(searchTerm) ||
1494
+ row.Version.toLowerCase().includes(searchTerm)
1492
1495
  );
1493
- PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1496
+ PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1494
1497
  }
1495
1498
  } catch (err) {
1496
1499
  console.error("Error refreshing runners data:", err);
@@ -1516,7 +1519,7 @@
1516
1519
  const searchTerm = e.target.value.toLowerCase();
1517
1520
 
1518
1521
  if (!searchTerm) {
1519
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1522
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1520
1523
  } else {
1521
1524
  const filtered = allRunnersRows.filter(row =>
1522
1525
  row.ID.toString().toLowerCase().includes(searchTerm) ||
@@ -1524,9 +1527,10 @@
1524
1527
  row.OS.toLowerCase().includes(searchTerm) ||
1525
1528
  row.Status.toLowerCase().includes(searchTerm) ||
1526
1529
  row.Busy.toLowerCase().includes(searchTerm) ||
1527
- row.Labels.toLowerCase().includes(searchTerm)
1530
+ row.Labels.toLowerCase().includes(searchTerm) ||
1531
+ row.Version.toLowerCase().includes(searchTerm)
1528
1532
  );
1529
- PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1533
+ PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1530
1534
  }
1531
1535
  });
1532
1536
  }
@@ -1359,7 +1359,7 @@
1359
1359
  if (!PAG_RUNNERS_TAB) return; // Runners tab not enabled
1360
1360
 
1361
1361
  if (runnersDataLoaded) {
1362
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1362
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1363
1363
  return;
1364
1364
  }
1365
1365
 
@@ -1381,10 +1381,11 @@
1381
1381
  OS: runner.os || "—",
1382
1382
  Status: runner.status || "—",
1383
1383
  Busy: runner.busy ? "Yes" : "No",
1384
- Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—")
1384
+ Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—"),
1385
+ Version: runner.version || "—"
1385
1386
  }));
1386
1387
 
1387
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1388
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1388
1389
  } catch (err) {
1389
1390
  console.error("Error loading runners data:", err);
1390
1391
  runnersMainThead.innerHTML = '<tr><th>Error</th></tr>';
@@ -1406,13 +1407,14 @@
1406
1407
  OS: runner.os || "—",
1407
1408
  Status: runner.status || "—",
1408
1409
  Busy: runner.busy ? "Yes" : "No",
1409
- Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—")
1410
+ Labels: Array.isArray(runner.labels) ? runner.labels.join(", ") : (runner.labels || "—"),
1411
+ Version: runner.version || "—"
1410
1412
  }));
1411
1413
 
1412
1414
  // Reapply current search filter if any
1413
1415
  const searchTerm = runnersSearchInput?.value?.toLowerCase() || '';
1414
1416
  if (!searchTerm) {
1415
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1417
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1416
1418
  } else {
1417
1419
  const filtered = allRunnersRows.filter(row =>
1418
1420
  row.ID.toString().toLowerCase().includes(searchTerm) ||
@@ -1420,9 +1422,10 @@
1420
1422
  row.OS.toLowerCase().includes(searchTerm) ||
1421
1423
  row.Status.toLowerCase().includes(searchTerm) ||
1422
1424
  row.Busy.toLowerCase().includes(searchTerm) ||
1423
- row.Labels.toLowerCase().includes(searchTerm)
1425
+ row.Labels.toLowerCase().includes(searchTerm) ||
1426
+ row.Version.toLowerCase().includes(searchTerm)
1424
1427
  );
1425
- PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1428
+ PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1426
1429
  }
1427
1430
  } catch (err) {
1428
1431
  console.error("Error refreshing runners data:", err);
@@ -1448,7 +1451,7 @@
1448
1451
  const searchTerm = e.target.value.toLowerCase();
1449
1452
 
1450
1453
  if (!searchTerm) {
1451
- PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1454
+ PAG_RUNNERS_TAB.setData(allRunnersRows, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1452
1455
  } else {
1453
1456
  const filtered = allRunnersRows.filter(row =>
1454
1457
  row.ID.toString().toLowerCase().includes(searchTerm) ||
@@ -1456,9 +1459,10 @@
1456
1459
  row.OS.toLowerCase().includes(searchTerm) ||
1457
1460
  row.Status.toLowerCase().includes(searchTerm) ||
1458
1461
  row.Busy.toLowerCase().includes(searchTerm) ||
1459
- row.Labels.toLowerCase().includes(searchTerm)
1462
+ row.Labels.toLowerCase().includes(searchTerm) ||
1463
+ row.Version.toLowerCase().includes(searchTerm)
1460
1464
  );
1461
- PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels"]);
1465
+ PAG_RUNNERS_TAB.setData(filtered, ["ID", "Name", "OS", "Status", "Busy", "Labels", "Version"]);
1462
1466
  }
1463
1467
  });
1464
1468
  }
@@ -305,7 +305,7 @@
305
305
  <div id="runners-content" class="container">
306
306
  <section class="panel">
307
307
  <div class="runners-search">
308
- <input type="text" id="runners-search-input" placeholder="Search GitHub Runners by id, name, os, status, or labels...">
308
+ <input type="text" id="runners-search-input" placeholder="Search GitHub Runners by id, name, os, status, labels, or version...">
309
309
  </div>
310
310
  <table class="table" id="runners-main-table">
311
311
  <thead></thead>
@@ -1 +1 @@
1
- __version__ = "5.0.2"
1
+ __version__ = "5.0.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyObservability
3
- Version: 5.0.2
3
+ Version: 5.0.4
4
4
  Summary: Lightweight OS-agnostic observability UI for PyNinja
5
5
  Author-email: Vignesh Rao <svignesh1793@gmail.com>
6
6
  License: MIT License
@@ -41,17 +41,17 @@ Classifier: Topic :: System :: Monitoring
41
41
  Requires-Python: >=3.11
42
42
  Description-Content-Type: text/markdown
43
43
  License-File: LICENSE
44
- Requires-Dist: aiohttp==3.13.*
45
- Requires-Dist: fastapi==0.135.*
46
- Requires-Dist: FastAPI-UI-Auth==0.3.*
44
+ Requires-Dist: aiohttp==3.14.*
45
+ Requires-Dist: fastapi==0.138.*
46
+ Requires-Dist: FastAPI-UI-Auth==0.4.*
47
47
  Requires-Dist: Jinja2==3.1.*
48
- Requires-Dist: prometheus_client==0.24.*
49
- Requires-Dist: pydantic==2.12.*
50
- Requires-Dist: pydantic-settings==2.13.*
48
+ Requires-Dist: prometheus_client==0.25.*
49
+ Requires-Dist: pydantic==2.13.*
50
+ Requires-Dist: pydantic-settings==2.14.*
51
51
  Requires-Dist: python-dotenv==1.2.*
52
52
  Requires-Dist: python-socketio==5.16.*
53
- Requires-Dist: requests==2.32.*
54
- Requires-Dist: uvicorn[standard]==0.41.*
53
+ Requires-Dist: requests==2.*
54
+ Requires-Dist: uvicorn[standard]==0.49.*
55
55
  Requires-Dist: websocket-client==1.9.*
56
56
  Provides-Extra: dev
57
57
  Requires-Dist: pre-commit; extra == "dev"
@@ -0,0 +1,22 @@
1
+ pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
2
+ pyobservability/github.py,sha256=cqCzwreGRWYwEKjKmkkccYouK5_MIR_n1LO_hOTliEU,3977
3
+ pyobservability/kuma.py,sha256=OFgrLdX5lXz8VRMx19ZCV6AKGODZZVLW3su50in29dc,4988
4
+ pyobservability/main.py,sha256=TnSdyjYZnJlbjoJhZtXA0LVjCCjZHxTP2mPl8Nvr6RI,7890
5
+ pyobservability/monitor.py,sha256=F4DWwJ-i0f-jnvjLtlSiRoRfjeVTTRKzMZnaDbITX7k,8600
6
+ pyobservability/prometheus.py,sha256=p1-qyd9vAX6mJzjXJh-xNSKJGsXsqT6Kzq4ngUFBai4,3197
7
+ pyobservability/transport.py,sha256=Wsxfemu87zSExIyErRgEu4OfBW6XmLxzqiieQ-zfT_4,10087
8
+ pyobservability/version.py,sha256=3nM3sU0hL2BMW8RKPuRUBMY441Ry8LtO9y1PnsRlqfA,22
9
+ pyobservability/config/enums.py,sha256=RDUtpDJ31maBnR1wPYN10izsLiSFFlbmvIMyv4g0zNw,363
10
+ pyobservability/config/settings.py,sha256=iQ8VO-8cVtJpAV4hphvqlwtMTnkOl2SxlbyMMyWvVd4,9567
11
+ pyobservability/config/squire.py,sha256=AO1D5DokKsc1CgjKQLcJCehoQvaKhdgbv6p3e1xQZ-Y,590
12
+ pyobservability/static/app.js,sha256=uunOgNLd4C8f0r8c-uar2I9AlJ4x4UfPKUNqUID7-gE,66313
13
+ pyobservability/static/styles.css,sha256=PZQ9saVpN6bKs9je--UpDFHif8xassF07bAVVZy-_dM,16376
14
+ pyobservability/static_legacy/app.js,sha256=UTkS2LUs52uy38kdI4oJu5E8-KhpIM3xrNoZIn7TOHg,63282
15
+ pyobservability/static_legacy/styles.css,sha256=GfQr4AfMTAVld-OAT-9ptwQK8X9avL0ZXWW_0HGpy2M,10212
16
+ pyobservability/templates/index.html,sha256=8yJssrH8kdykjJFoAy5PuBea_13wHfAwEwGX4z6EDE8,13102
17
+ pyobservability-5.0.4.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
18
+ pyobservability-5.0.4.dist-info/METADATA,sha256=FC3qlnAkCy9N0CDJecqzSpfuErnagbtKYPpGdir_kfY,9671
19
+ pyobservability-5.0.4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
20
+ pyobservability-5.0.4.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
21
+ pyobservability-5.0.4.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
22
+ pyobservability-5.0.4.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- pyobservability/__init__.py,sha256=yVBLyTohBiBKp0Otyl04IggPh8mhg3Er25u6eFyxMto,2618
2
- pyobservability/github.py,sha256=ORChfqUwLd4EycU9dXzlNMbGzF_YywVOdwRE3AMkeaU,3339
3
- pyobservability/kuma.py,sha256=x44YzGuKHnCKdzcp1iusRCCasN4y6L4p9RVdb1YDWdE,4850
4
- pyobservability/main.py,sha256=TnSdyjYZnJlbjoJhZtXA0LVjCCjZHxTP2mPl8Nvr6RI,7890
5
- pyobservability/monitor.py,sha256=F4DWwJ-i0f-jnvjLtlSiRoRfjeVTTRKzMZnaDbITX7k,8600
6
- pyobservability/prometheus.py,sha256=p1-qyd9vAX6mJzjXJh-xNSKJGsXsqT6Kzq4ngUFBai4,3197
7
- pyobservability/transport.py,sha256=Wsxfemu87zSExIyErRgEu4OfBW6XmLxzqiieQ-zfT_4,10087
8
- pyobservability/version.py,sha256=xFH0agSAen6eEDzd6JDo0wynm0lQWngI4rS0ugCXpkc,22
9
- pyobservability/config/enums.py,sha256=RDUtpDJ31maBnR1wPYN10izsLiSFFlbmvIMyv4g0zNw,363
10
- pyobservability/config/settings.py,sha256=6ddFsrujr3giiMalvRLqk5mVsSFAC4eUekXW6EfoGIk,7970
11
- pyobservability/config/squire.py,sha256=AO1D5DokKsc1CgjKQLcJCehoQvaKhdgbv6p3e1xQZ-Y,590
12
- pyobservability/static/app.js,sha256=FDwgxMqskJHU5C6Z9jTAJbFIbnaJ17zYctJMwCxAl-g,66007
13
- pyobservability/static/styles.css,sha256=PZQ9saVpN6bKs9je--UpDFHif8xassF07bAVVZy-_dM,16376
14
- pyobservability/static_legacy/app.js,sha256=EFhLWJnIA1v1JRpXhIcwUVS4KdmNN0gyWjQyWvS847c,62976
15
- pyobservability/static_legacy/styles.css,sha256=GfQr4AfMTAVld-OAT-9ptwQK8X9avL0ZXWW_0HGpy2M,10212
16
- pyobservability/templates/index.html,sha256=_Im-QfDHbNFHVERjjZICqpK0Ub4m5KXmwlmdKFe4g4Q,13093
17
- pyobservability-5.0.2.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
18
- pyobservability-5.0.2.dist-info/METADATA,sha256=4izyvOmiii_nywNOSd12tCVV3OSslmWtxhDYB6kvcaw,9674
19
- pyobservability-5.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
20
- pyobservability-5.0.2.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
21
- pyobservability-5.0.2.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
22
- pyobservability-5.0.2.dist-info/RECORD,,