forked from ariska/karung-web-admin
update adjusting for more than 1 source for each kandang
This commit is contained in:
1 parent
1157d4e8be
commit
3823b511ba
3 files changed
+108
-48
No files matched your search
@@ -11,7 +11,14 @@ Admin dashboard that aggregates **KTC** (tuang) and **KPC** (masuk) counter data
|
|||||||
|
|
||||||
Host IPs are **not** hardcoded. Configure them per machine in `sources.env`.
|
Host IPs are **not** hardcoded. Configure them per machine in `sources.env`.
|
||||||
|
|
||||||
Multiple hosts per role (comma-separated) are labeled by env order. Tuang combines each host's left/right counts into `K{n} - Use`; masuk keeps `K{n} - In` and `K{n} - Out`.
|
Source URL grouping:
|
||||||
|
|
||||||
|
- `|` separates cameras (`K1`, `K2`, ...)
|
||||||
|
- `,` separates hosts inside one camera (their counts are summed into that `Kn`)
|
||||||
|
- No `|` keeps legacy behavior: each comma-separated host is its own `Kn`
|
||||||
|
- One multi-host camera only: use a trailing `|` (e.g. `http://a,http://b,http://c|`)
|
||||||
|
|
||||||
|
Tuang uses each host's `total_count` as `K{n} - Use`; masuk keeps `K{n} - In` and `K{n} - Out`.
|
||||||
|
|
||||||
## Install path
|
## Install path
|
||||||
|
|
||||||
@@ -28,12 +35,16 @@ nano sources.env # set real KTC/KPC URLs for this site
|
|||||||
Example `sources.env`:
|
Example `sources.env`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
KTC_BASE_URL=http://10.0.0.11:9000,http://10.0.0.12:9000
|
# K1 = three hosts summed; K2 = one host
|
||||||
KPC_BASE_URL=http://10.0.0.11:7000,http://10.0.0.12:7000
|
KTC_BASE_URL=http://10.0.0.11:9000,http://10.0.0.12:9000,http://10.0.0.13:9000|http://10.0.0.14:9000
|
||||||
|
# Same grouping for masuk (In/Out summed per Kn)
|
||||||
|
KPC_BASE_URL=http://10.0.0.11:7000,http://10.0.0.12:7000,http://10.0.0.13:7000|http://10.0.0.14:7000
|
||||||
SOURCE_HISTORY_DAYS=30
|
SOURCE_HISTORY_DAYS=30
|
||||||
SOURCE_PULL_INTERVAL_SECONDS=30
|
SOURCE_PULL_INTERVAL_SECONDS=30
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Legacy (unchanged): `http://a:9000,http://b:9000` still means `K1` and `K2` (same for KPC).
|
||||||
|
|
||||||
After editing:
|
After editing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -95,8 +106,8 @@ Configured in `sources.env` (see `sources.env.example`):
|
|||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `KTC_BASE_URL` | Comma-separated tuang/KTC base URLs (summed) |
|
| `KTC_BASE_URL` | Tuang/KTC source groups (`\|` = cameras, `,` = hosts in a camera) |
|
||||||
| `KPC_BASE_URL` | Comma-separated masuk/KPC base URLs (summed) |
|
| `KPC_BASE_URL` | Masuk/KPC source groups (same grouping syntax) |
|
||||||
| `SOURCE_HISTORY_DAYS` | History window to pull (default `30`) |
|
| `SOURCE_HISTORY_DAYS` | History window to pull (default `30`) |
|
||||||
| `SOURCE_PULL_INTERVAL_SECONDS` | Pull loop interval (default `30`) |
|
| `SOURCE_PULL_INTERVAL_SECONDS` | Pull loop interval (default `30`) |
|
||||||
| `KARUNG_TUANG_JSON` | Live tuang JSON path (default `karung_tuang.json`) |
|
| `KARUNG_TUANG_JSON` | Live tuang JSON path (default `karung_tuang.json`) |
|
||||||
|
|||||||
+79
-39
@@ -2,9 +2,15 @@
|
|||||||
"""
|
"""
|
||||||
Pull counter data from KTC (tuang) and KPC (masuk) dashboards into local JSON + SQLite.
|
Pull counter data from KTC (tuang) and KPC (masuk) dashboards into local JSON + SQLite.
|
||||||
|
|
||||||
Multiple base URLs (comma-separated) are labeled by env order:
|
Source URL groups are labeled by env order:
|
||||||
tuang -> K1 - Use, K2 - Use, ... (left + right combined per host)
|
tuang -> K1 - Use, K2 - Use, ... (API total_count; hosts in a group are summed)
|
||||||
masuk -> K1 - In / K1 - Out, K2 - In / K2 - Out, ...
|
masuk -> K1 - In / K1 - Out, K2 - In / K2 - Out, ...
|
||||||
|
|
||||||
|
Grouping syntax in KTC_BASE_URL / KPC_BASE_URL:
|
||||||
|
- `|` separates cameras (K1, K2, ...)
|
||||||
|
- `,` separates hosts inside one camera (summed into that Kn)
|
||||||
|
- No `|` keeps legacy behavior: each comma-separated host is its own Kn
|
||||||
|
- Single multi-host camera: `http://a,http://b,http://c|` (trailing `|`)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -27,17 +33,16 @@ PULL_STATUS_FILE = BASE_DIR / ".pull_status.json"
|
|||||||
SOURCES_ENV_FILE = BASE_DIR / "sources.env"
|
SOURCES_ENV_FILE = BASE_DIR / "sources.env"
|
||||||
|
|
||||||
# Field suffix is combined with source index into names like "K1 - Use".
|
# Field suffix is combined with source index into names like "K1 - Use".
|
||||||
|
# KTC uses total_count so hosts without left/right still report correctly.
|
||||||
KTC_LIVE_FIELDS = (
|
KTC_LIVE_FIELDS = (
|
||||||
("count_left", "Use"),
|
("total_count", "Use"),
|
||||||
("count_right", "Use"),
|
|
||||||
)
|
)
|
||||||
KPC_LIVE_FIELDS = (
|
KPC_LIVE_FIELDS = (
|
||||||
("count_in", "In"),
|
("count_in", "In"),
|
||||||
("count_out", "Out"),
|
("count_out", "Out"),
|
||||||
)
|
)
|
||||||
KTC_HISTORY_FIELDS = (
|
KTC_HISTORY_FIELDS = (
|
||||||
("total_left", "Use"),
|
("total_count", "Use"),
|
||||||
("total_right", "Use"),
|
|
||||||
)
|
)
|
||||||
KPC_HISTORY_FIELDS = (
|
KPC_HISTORY_FIELDS = (
|
||||||
("total_in", "In"),
|
("total_in", "In"),
|
||||||
@@ -79,7 +84,7 @@ def get_int_env(env_name, default_value):
|
|||||||
|
|
||||||
|
|
||||||
def parse_base_urls(raw_value):
|
def parse_base_urls(raw_value):
|
||||||
"""Parse comma-separated base URLs."""
|
"""Parse comma-separated base URLs into a flat list."""
|
||||||
urls = []
|
urls = []
|
||||||
for part in str(raw_value or "").split(","):
|
for part in str(raw_value or "").split(","):
|
||||||
url = part.strip().rstrip("/")
|
url = part.strip().rstrip("/")
|
||||||
@@ -88,11 +93,33 @@ def parse_base_urls(raw_value):
|
|||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
|
||||||
def get_required_base_urls(env_name):
|
def parse_source_groups(raw_value):
|
||||||
"""Read required comma-separated source URLs from the environment."""
|
"""Parse source URL groups for Kn labeling.
|
||||||
urls = parse_base_urls(os.environ.get(env_name, ""))
|
|
||||||
if urls:
|
With `|`: each segment is one Kn; commas inside a segment are hosts summed into that Kn.
|
||||||
return urls
|
Without `|`: legacy mode — each comma-separated host is its own Kn.
|
||||||
|
Trailing `|` enables group mode for a single multi-host camera.
|
||||||
|
"""
|
||||||
|
raw = str(raw_value or "").strip()
|
||||||
|
if not raw:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if "|" in raw:
|
||||||
|
groups = []
|
||||||
|
for segment in raw.split("|"):
|
||||||
|
urls = parse_base_urls(segment)
|
||||||
|
if urls:
|
||||||
|
groups.append(urls)
|
||||||
|
return groups
|
||||||
|
|
||||||
|
return [[url] for url in parse_base_urls(raw)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_required_source_groups(env_name):
|
||||||
|
"""Read required source URL groups from the environment."""
|
||||||
|
groups = parse_source_groups(os.environ.get(env_name, ""))
|
||||||
|
if groups:
|
||||||
|
return groups
|
||||||
|
|
||||||
example_path = BASE_DIR / "sources.env.example"
|
example_path = BASE_DIR / "sources.env.example"
|
||||||
raise SystemExit(
|
raise SystemExit(
|
||||||
@@ -102,6 +129,11 @@ def get_required_base_urls(env_name):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_source_groups(groups):
|
||||||
|
"""Flatten groups into a host list (order preserved)."""
|
||||||
|
return [url for group in groups for url in group]
|
||||||
|
|
||||||
|
|
||||||
def build_source_camera_name(source_index, camera_suffix):
|
def build_source_camera_name(source_index, camera_suffix):
|
||||||
return f"K{source_index} - {camera_suffix}"
|
return f"K{source_index} - {camera_suffix}"
|
||||||
|
|
||||||
@@ -330,7 +362,7 @@ def fetch_host_payload(*, base_url, live_fields, history_fields, history_days, s
|
|||||||
def pull_source(
|
def pull_source(
|
||||||
*,
|
*,
|
||||||
source_key,
|
source_key,
|
||||||
base_urls,
|
source_groups,
|
||||||
live_fields,
|
live_fields,
|
||||||
history_fields,
|
history_fields,
|
||||||
json_path,
|
json_path,
|
||||||
@@ -338,32 +370,38 @@ def pull_source(
|
|||||||
table_name,
|
table_name,
|
||||||
history_days,
|
history_days,
|
||||||
):
|
):
|
||||||
"""Pull one or more hosts for a role and write labeled live/history totals."""
|
"""Pull grouped hosts for a role and write labeled live/history totals.
|
||||||
|
|
||||||
|
Each group shares one Kn label; host totals inside a group are summed.
|
||||||
|
"""
|
||||||
hosts = []
|
hosts = []
|
||||||
live_payloads = []
|
live_payloads = []
|
||||||
history_groups = []
|
history_groups = []
|
||||||
|
base_urls = flatten_source_groups(source_groups)
|
||||||
|
|
||||||
for source_index, base_url in enumerate(base_urls, start=1):
|
for source_index, group_urls in enumerate(source_groups, start=1):
|
||||||
host = fetch_host_payload(
|
for base_url in group_urls:
|
||||||
base_url=base_url,
|
host = fetch_host_payload(
|
||||||
live_fields=live_fields,
|
base_url=base_url,
|
||||||
history_fields=history_fields,
|
live_fields=live_fields,
|
||||||
history_days=history_days,
|
history_fields=history_fields,
|
||||||
source_index=source_index,
|
history_days=history_days,
|
||||||
)
|
source_index=source_index,
|
||||||
hosts.append(
|
)
|
||||||
{
|
hosts.append(
|
||||||
"status": host["status"],
|
{
|
||||||
"pulled_at": host["pulled_at"],
|
"status": host["status"],
|
||||||
"base_url": host["base_url"],
|
"pulled_at": host["pulled_at"],
|
||||||
"error": host["error"],
|
"base_url": host["base_url"],
|
||||||
"live_cameras": host.get("live_cameras", []),
|
"source_index": source_index,
|
||||||
"history_rows": host.get("history_row_count", 0),
|
"error": host["error"],
|
||||||
}
|
"live_cameras": host.get("live_cameras", []),
|
||||||
)
|
"history_rows": host.get("history_row_count", 0),
|
||||||
if host["status"] == "ok":
|
}
|
||||||
live_payloads.append(host["live_data"])
|
)
|
||||||
history_groups.append(host["history_rows"])
|
if host["status"] == "ok":
|
||||||
|
live_payloads.append(host["live_data"])
|
||||||
|
history_groups.append(host["history_rows"])
|
||||||
|
|
||||||
ok_count = sum(1 for host in hosts if host["status"] == "ok")
|
ok_count = sum(1 for host in hosts if host["status"] == "ok")
|
||||||
if ok_count == 0:
|
if ok_count == 0:
|
||||||
@@ -387,6 +425,7 @@ def pull_source(
|
|||||||
"pulled_at": get_utc_timestamp(),
|
"pulled_at": get_utc_timestamp(),
|
||||||
"base_url": base_urls[0] if len(base_urls) == 1 else None,
|
"base_url": base_urls[0] if len(base_urls) == 1 else None,
|
||||||
"base_urls": list(base_urls),
|
"base_urls": list(base_urls),
|
||||||
|
"source_groups": [list(group) for group in source_groups],
|
||||||
"combine": "sum",
|
"combine": "sum",
|
||||||
"hosts": hosts,
|
"hosts": hosts,
|
||||||
"error": "; ".join(errors) if errors else None,
|
"error": "; ".join(errors) if errors else None,
|
||||||
@@ -394,6 +433,7 @@ def pull_source(
|
|||||||
"history_rows": len(combined_history),
|
"history_rows": len(combined_history),
|
||||||
"hosts_ok": ok_count,
|
"hosts_ok": ok_count,
|
||||||
"hosts_total": len(hosts),
|
"hosts_total": len(hosts),
|
||||||
|
"groups_total": len(source_groups),
|
||||||
"source_key": source_key,
|
"source_key": source_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,8 +452,8 @@ def format_role_status(role_status):
|
|||||||
|
|
||||||
|
|
||||||
def run_pull():
|
def run_pull():
|
||||||
ktc_urls = get_required_base_urls("KTC_BASE_URL")
|
ktc_groups = get_required_source_groups("KTC_BASE_URL")
|
||||||
kpc_urls = get_required_base_urls("KPC_BASE_URL")
|
kpc_groups = get_required_source_groups("KPC_BASE_URL")
|
||||||
history_days = get_int_env("SOURCE_HISTORY_DAYS", DEFAULT_HISTORY_DAYS)
|
history_days = get_int_env("SOURCE_HISTORY_DAYS", DEFAULT_HISTORY_DAYS)
|
||||||
|
|
||||||
tuang_json = get_configured_path("KARUNG_TUANG_JSON", "karung_tuang.json")
|
tuang_json = get_configured_path("KARUNG_TUANG_JSON", "karung_tuang.json")
|
||||||
@@ -424,7 +464,7 @@ def run_pull():
|
|||||||
status = load_pull_status()
|
status = load_pull_status()
|
||||||
status["ktc"] = pull_source(
|
status["ktc"] = pull_source(
|
||||||
source_key="ktc",
|
source_key="ktc",
|
||||||
base_urls=ktc_urls,
|
source_groups=ktc_groups,
|
||||||
live_fields=KTC_LIVE_FIELDS,
|
live_fields=KTC_LIVE_FIELDS,
|
||||||
history_fields=KTC_HISTORY_FIELDS,
|
history_fields=KTC_HISTORY_FIELDS,
|
||||||
json_path=tuang_json,
|
json_path=tuang_json,
|
||||||
@@ -434,7 +474,7 @@ def run_pull():
|
|||||||
)
|
)
|
||||||
status["kpc"] = pull_source(
|
status["kpc"] = pull_source(
|
||||||
source_key="kpc",
|
source_key="kpc",
|
||||||
base_urls=kpc_urls,
|
source_groups=kpc_groups,
|
||||||
live_fields=KPC_LIVE_FIELDS,
|
live_fields=KPC_LIVE_FIELDS,
|
||||||
history_fields=KPC_HISTORY_FIELDS,
|
history_fields=KPC_HISTORY_FIELDS,
|
||||||
json_path=masuk_json,
|
json_path=masuk_json,
|
||||||
|
|||||||
+13
-4
@@ -2,15 +2,24 @@
|
|||||||
# Copy this file once per machine, then edit the IPs for that site:
|
# Copy this file once per machine, then edit the IPs for that site:
|
||||||
# cp sources.env.example sources.env
|
# cp sources.env.example sources.env
|
||||||
#
|
#
|
||||||
# Comma-separated hosts are summed into one site total.
|
# Grouping:
|
||||||
|
# `|` separates cameras (K1, K2, ...)
|
||||||
|
# `,` separates hosts inside one camera (summed into that Kn)
|
||||||
|
# No `|` keeps legacy behavior: each host is its own Kn
|
||||||
|
# One camera from many hosts: add a trailing `|`
|
||||||
|
#
|
||||||
# Restart after changes:
|
# Restart after changes:
|
||||||
# sudo systemctl restart karung-web-admin-pull-sources.service
|
# sudo systemctl restart karung-web-admin-pull-sources.service
|
||||||
|
|
||||||
# Tuang (KTC)
|
# Tuang (KTC)
|
||||||
KTC_BASE_URL=http://REPLACE_HOST_A:9000,http://REPLACE_HOST_B:9000
|
# Example: K1 = three hosts summed, K2 = one host
|
||||||
|
# KTC_BASE_URL=http://REPLACE_HOST_A:9000,http://REPLACE_HOST_B:9000,http://REPLACE_HOST_C:9000|http://REPLACE_HOST_D:9000
|
||||||
|
KTC_BASE_URL=http://REPLACE_HOST_A:9000|http://REPLACE_HOST_B:9000
|
||||||
|
|
||||||
# Masuk (KPC)
|
# Masuk (KPC) — same grouping; hosts in a group sum into Kn - In / Kn - Out
|
||||||
KPC_BASE_URL=http://REPLACE_HOST_A:7000,http://REPLACE_HOST_B:7000
|
# Example: K1 = three hosts summed, K2 = two hosts summed
|
||||||
|
# KPC_BASE_URL=http://REPLACE_HOST_A:7000,http://REPLACE_HOST_B:7000,http://REPLACE_HOST_C:7000|http://REPLACE_HOST_D:7000,http://REPLACE_HOST_E:7000
|
||||||
|
KPC_BASE_URL=http://REPLACE_HOST_A:7000|http://REPLACE_HOST_B:7000
|
||||||
|
|
||||||
SOURCE_HISTORY_DAYS=30
|
SOURCE_HISTORY_DAYS=30
|
||||||
SOURCE_PULL_INTERVAL_SECONDS=30
|
SOURCE_PULL_INTERVAL_SECONDS=30
|
||||||
Reference in new issue
Block a user