remoteRF-server 1.1.0.dev35__py3-none-any.whl → 1.1.0.dev36__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.
- remoteRF_server/server/federation/cli.py +15 -5
- remoteRF_server/server/federation/cluster.py +55 -9
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/METADATA +1 -1
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/RECORD +8 -8
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/WHEEL +0 -0
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/entry_points.txt +0 -0
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/licenses/LICENSE +0 -0
- {remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/top_level.txt +0 -0
|
@@ -38,6 +38,10 @@ def _lab(party: dict) -> str:
|
|
|
38
38
|
return f"{party['display_name']} ({short}…)" if party.get("display_name") else party["deployment_id"]
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def _n(count: int, noun: str) -> str:
|
|
42
|
+
return f"{count} {noun}{'' if count == 1 else 's'}"
|
|
43
|
+
|
|
44
|
+
|
|
41
45
|
def _day(epoch) -> str:
|
|
42
46
|
import datetime
|
|
43
47
|
|
|
@@ -64,6 +68,7 @@ def cmd_clusters() -> int:
|
|
|
64
68
|
for index, item in enumerate(items):
|
|
65
69
|
if index:
|
|
66
70
|
rows.append("")
|
|
71
|
+
owner_row = None
|
|
67
72
|
if item["role"] == "owner":
|
|
68
73
|
invites = item["open_invites"]
|
|
69
74
|
rows.append(f"{item['name']} (yours)" + (f" {invites} open invite{'s' if invites != 1 else ''}" if invites else ""))
|
|
@@ -72,20 +77,25 @@ def cmd_clusters() -> int:
|
|
|
72
77
|
if not item["members"] and not invites:
|
|
73
78
|
rows.append(f" nobody yet: `cluster invite {item['name']}`")
|
|
74
79
|
else:
|
|
75
|
-
owner = {"display_name": item.get("peer_name"), "deployment_id": item["peer"]}
|
|
80
|
+
owner_row = owner = {"display_name": item.get("peer_name"), "deployment_id": item["peer"]}
|
|
76
81
|
rows.append(f"{item['name']} {item['state']}")
|
|
77
82
|
labs = [(owner, "admin", "", None), (me, "member", "", None)] + [
|
|
78
83
|
(p, "member", p["state"] or "joining", None) for p in item.get("peers", [])]
|
|
79
84
|
for lab, role, lab_state, since in labs:
|
|
80
|
-
you =
|
|
81
|
-
line = f" {_lab(lab)} {role}
|
|
85
|
+
you = lab["deployment_id"] == me["deployment_id"]
|
|
86
|
+
line = f" {_lab(lab)} {role}" + (" (you)" if you else "")
|
|
82
87
|
if lab_state and lab_state != "active":
|
|
83
88
|
line += f" {lab_state}"
|
|
84
89
|
if since:
|
|
85
90
|
line += f" since {_day(since)}"
|
|
86
91
|
rows.append(line)
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
if you:
|
|
93
|
+
rows.append(" sharing: " + (", ".join(
|
|
94
|
+
f"{g['group_name']} ({_n(len(g['devices']), 'device')})" for g in item["groups"]) or "nothing"))
|
|
95
|
+
else:
|
|
96
|
+
sharing = lab.get("sharing") if lab is not owner_row else item.get("peer_sharing")
|
|
97
|
+
if sharing:
|
|
98
|
+
rows.append(f" sharing {_n(sharing['groups'], 'group')}, {_n(sharing['devices'], 'device')}")
|
|
89
99
|
rows.append(f" {item['students']} of your users enrolled, {item['visitors']} visiting")
|
|
90
100
|
print()
|
|
91
101
|
print_panel("Clusters", rows, literal=True)
|
|
@@ -404,7 +404,8 @@ def list_clusters() -> list[dict]:
|
|
|
404
404
|
except ContractError:
|
|
405
405
|
pass
|
|
406
406
|
members.append({"deployment_id": peer, "display_name": member.get("display_name"),
|
|
407
|
-
"state": state, "joined_at": member.get("joined_at")
|
|
407
|
+
"state": state, "joined_at": member.get("joined_at"),
|
|
408
|
+
"sharing": member.get("sharing")})
|
|
408
409
|
item.update(members=members, open_invites=len(cluster["invites"]))
|
|
409
410
|
else:
|
|
410
411
|
state = cluster.get("state")
|
|
@@ -414,9 +415,11 @@ def list_clusters() -> list[dict]:
|
|
|
414
415
|
state = store.show(cluster["contract_id"], 1)["state"]
|
|
415
416
|
except ContractError:
|
|
416
417
|
pass
|
|
417
|
-
peers = [{"deployment_id": peer, "display_name": party.get("display_name"), "state": party.get("state")
|
|
418
|
+
peers = [{"deployment_id": peer, "display_name": party.get("display_name"), "state": party.get("state"),
|
|
419
|
+
"sharing": party.get("sharing")}
|
|
418
420
|
for peer, party in sorted(cluster.get("peers", {}).items())]
|
|
419
|
-
item.update(peer=cluster["peer"], peer_name=cluster.get("peer_name"), state=state, peers=peers
|
|
421
|
+
item.update(peer=cluster["peer"], peer_name=cluster.get("peer_name"), state=state, peers=peers,
|
|
422
|
+
peer_sharing=cluster.get("sharing"))
|
|
420
423
|
item.update(groups=groups(name), **_account_counts(name))
|
|
421
424
|
out.append(item)
|
|
422
425
|
return out
|
|
@@ -445,12 +448,23 @@ def groups(name: str) -> list[dict]:
|
|
|
445
448
|
out = []
|
|
446
449
|
with _usergroups() as conn:
|
|
447
450
|
for group_id in cluster.get("groups", []):
|
|
448
|
-
row = conn.execute("SELECT group_name FROM UserGroups WHERE group_id=?",
|
|
451
|
+
row = conn.execute("SELECT group_name,devices_whitelist FROM UserGroups WHERE group_id=?",
|
|
452
|
+
(group_id,)).fetchone()
|
|
449
453
|
if row is not None:
|
|
450
|
-
|
|
454
|
+
try:
|
|
455
|
+
devices = {int(d) for d in json.loads(row[1] or "[]")}
|
|
456
|
+
except (ValueError, TypeError):
|
|
457
|
+
devices = set()
|
|
458
|
+
out.append({"group_id": group_id, "group_name": row[0], "devices": sorted(devices)})
|
|
451
459
|
return out
|
|
452
460
|
|
|
453
461
|
|
|
462
|
+
def _sharing(name: str) -> dict:
|
|
463
|
+
"""Counts only -- all a partner ever learns about what we share."""
|
|
464
|
+
shared = groups(name)
|
|
465
|
+
return {"groups": len(shared), "devices": len({d for g in shared for d in g["devices"]})}
|
|
466
|
+
|
|
467
|
+
|
|
454
468
|
def _account_counts(name: str) -> dict:
|
|
455
469
|
with _usergroups() as conn:
|
|
456
470
|
rows = dict(conn.execute(
|
|
@@ -687,6 +701,32 @@ def _publish_roster(name: str, cluster: dict) -> None:
|
|
|
687
701
|
cluster["roster"] = active
|
|
688
702
|
|
|
689
703
|
|
|
704
|
+
def _publish_status(name: str, cluster: dict) -> None:
|
|
705
|
+
"""Any lab: post our counts for this cluster when they change."""
|
|
706
|
+
sharing = _sharing(name)
|
|
707
|
+
if sharing == cluster.get("status"):
|
|
708
|
+
return
|
|
709
|
+
key, _, me = g._signing_material()
|
|
710
|
+
owner = me if cluster["role"] == "owner" else cluster["peer"]
|
|
711
|
+
status = {"cluster_name": name, "owner": owner, "deployment_id": me, "issued_at": int(time.time()), **sharing}
|
|
712
|
+
signature = base64.b64encode(key.sign(canonical_json(status).encode())).decode("ascii")
|
|
713
|
+
g.call("POST", "/v2/broker/clusters/roster", {"protocol_version": "2", "status": status, "signature": signature})
|
|
714
|
+
cluster["status"] = sharing
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def _take_statuses(name: str, cluster: dict, statuses: list[dict]) -> None:
|
|
718
|
+
"""Attach each partner's verified counts to its row."""
|
|
719
|
+
owner = g._signing_material()[2] if cluster["role"] == "owner" else cluster["peer"]
|
|
720
|
+
parties = dict(_parties(cluster))
|
|
721
|
+
for item in statuses:
|
|
722
|
+
status = item.get("status") or {}
|
|
723
|
+
peer = status.get("deployment_id")
|
|
724
|
+
if status.get("owner") != owner or status.get("cluster_name") != name or peer not in parties:
|
|
725
|
+
continue
|
|
726
|
+
if _roster_signed_by(peer, status, item.get("signature", "")):
|
|
727
|
+
parties[peer]["sharing"] = {"groups": status["groups"], "devices": status["devices"]}
|
|
728
|
+
|
|
729
|
+
|
|
690
730
|
def _roster_signed_by(owner: str, roster: dict, signature: str) -> bool:
|
|
691
731
|
from cryptography.exceptions import InvalidSignature
|
|
692
732
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
|
@@ -774,7 +814,7 @@ def tick(notify=None, home=None) -> None:
|
|
|
774
814
|
return
|
|
775
815
|
directory = None
|
|
776
816
|
broker_invites = None
|
|
777
|
-
|
|
817
|
+
board = None # rosters and status notes, one GET for all clusters
|
|
778
818
|
for name, cluster in clusters.items():
|
|
779
819
|
if cluster["role"] == "owner":
|
|
780
820
|
if cluster["invites"]:
|
|
@@ -797,9 +837,15 @@ def tick(notify=None, home=None) -> None:
|
|
|
797
837
|
if directory is None:
|
|
798
838
|
directory = g.call("GET", "/v2/broker/contracts")["contracts"]
|
|
799
839
|
_advance(cluster, cluster["peer"], directory, label=name, notify=notify, is_member=True)
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
840
|
+
if board is None:
|
|
841
|
+
board = g.call("GET", "/v2/broker/clusters/roster")
|
|
842
|
+
if cluster["role"] != "owner":
|
|
843
|
+
_mesh(name, cluster, board["rosters"], directory, notify)
|
|
844
|
+
try:
|
|
845
|
+
_publish_status(name, cluster)
|
|
846
|
+
except g.GlobalError as exc:
|
|
847
|
+
notify(f"[cluster] {name}: could not publish what we share: {exc}")
|
|
848
|
+
_take_statuses(name, cluster, board.get("statuses", []))
|
|
803
849
|
save(clusters)
|
|
804
850
|
if home is not None:
|
|
805
851
|
enroll_students(home, notify)
|
|
@@ -70,8 +70,8 @@ remoteRF_server/server/deployment/__init__.py,sha256=YV86uafd8XiGD7sLaqNfO5xxmt5
|
|
|
70
70
|
remoteRF_server/server/deployment/capabilities.py,sha256=20RxmGG03VoQQZnV3jGhc-3i9B5hOl5fCopnsErFjPk,872
|
|
71
71
|
remoteRF_server/server/federation/__init__.py,sha256=auqdGiGnEGIKQiXX2jFVt8iWmfyZ-V3w4dUmtWDKAZE,735
|
|
72
72
|
remoteRF_server/server/federation/admin.py,sha256=t3F17FIytekzclPICmaFJEA4-F5OuWcDx3rgmFga17A,9187
|
|
73
|
-
remoteRF_server/server/federation/cli.py,sha256=
|
|
74
|
-
remoteRF_server/server/federation/cluster.py,sha256=
|
|
73
|
+
remoteRF_server/server/federation/cli.py,sha256=iZpS-31516atRauI6zXdCTw81hV7QNEdyDqC8ijXoEA,11175
|
|
74
|
+
remoteRF_server/server/federation/cluster.py,sha256=m_SW942R7d0a8X16kMfKn3fdUbyJsAmonI67Wl8OIOs,35529
|
|
75
75
|
remoteRF_server/server/federation/contracts.py,sha256=NYh7lb_9etJHFfzXwevjC4HfSBUypLxg5xGeBgydLF4,29305
|
|
76
76
|
remoteRF_server/server/federation/destination.py,sha256=XawmGCiVbL_MfNNn8xwV9_bYxUDW1L7efCpL11K2JAc,35909
|
|
77
77
|
remoteRF_server/server/federation/devices.py,sha256=n5MvnD0UZyl4sFpiekG9ProanWXTYizYq6YchCflLgY,18061
|
|
@@ -90,9 +90,9 @@ remoteRF_server/tools/gen_certs.py,sha256=Vbyu8JjRu__UYPVmyCRXt5SOw0Za1HV7g1md3G
|
|
|
90
90
|
remoteRF_server/tools/gist_status.py,sha256=SFv2iQgQrIKT9gxKrU-_Dr7S0p0rE4wQM6J3OC6FcoM,4649
|
|
91
91
|
remoteRF_server/tools/gist_status_testing.py,sha256=IYw7UkPjAUpKDTtIlnYGb5g6SJkwz4TRGUfJJ6f4LhQ,2690
|
|
92
92
|
remoteRF_server/tools/introspect_uhd.py,sha256=vUpPb0rM9tsHPxWadS3EfMDE-gMd-TvNV0TjrZOQqBY,4531
|
|
93
|
-
remoterf_server-1.1.0.
|
|
94
|
-
remoterf_server-1.1.0.
|
|
95
|
-
remoterf_server-1.1.0.
|
|
96
|
-
remoterf_server-1.1.0.
|
|
97
|
-
remoterf_server-1.1.0.
|
|
98
|
-
remoterf_server-1.1.0.
|
|
93
|
+
remoterf_server-1.1.0.dev36.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
94
|
+
remoterf_server-1.1.0.dev36.dist-info/METADATA,sha256=PA_v4CEjH9HfN0EABXND3J1B53HdPVIhfxZhxwQ_vvc,42069
|
|
95
|
+
remoterf_server-1.1.0.dev36.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
96
|
+
remoterf_server-1.1.0.dev36.dist-info/entry_points.txt,sha256=_bbKk65PfYi-n6zUjqOiOwcalf_Ws1TCGATIe1TukxE,136
|
|
97
|
+
remoterf_server-1.1.0.dev36.dist-info/top_level.txt,sha256=NuYWe8dSx3qdbxshWBwOgx68EHmJ9I8Zvxam4ARsQ_I,30
|
|
98
|
+
remoterf_server-1.1.0.dev36.dist-info/RECORD,,
|
|
File without changes
|
{remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev35.dist-info → remoterf_server-1.1.0.dev36.dist-info}/top_level.txt
RENAMED
|
File without changes
|