remoteRF-server 1.1.0.dev34__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/commands.py +2 -2
- remoteRF_server/server/federation/cli.py +58 -11
- remoteRF_server/server/federation/cluster.py +55 -9
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/METADATA +1 -1
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/RECORD +9 -9
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/WHEEL +0 -0
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/entry_points.txt +0 -0
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/licenses/LICENSE +0 -0
- {remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/top_level.txt +0 -0
|
@@ -153,8 +153,8 @@ def _table() -> list[Command]:
|
|
|
153
153
|
Command(("cluster invite",), "Invite code for a lab: cluster invite <name>", _federation("invite"), "Clusters"),
|
|
154
154
|
Command(("cluster join",), "Join with a code: cluster join <code>", _federation("join"), "Clusters"),
|
|
155
155
|
Command(("cluster remove",), "Leave or disband: cluster remove <name>", _federation("remove"), "Clusters"),
|
|
156
|
-
Command(("cluster groups",), "
|
|
157
|
-
Command(("cluster codes",), "
|
|
156
|
+
Command(("cluster groups",), "Choose which of your groups a cluster shares (interactive)", _federation("groups"), "Clusters"),
|
|
157
|
+
Command(("cluster codes",), "Codes for your users to join a cluster: cluster codes [count]", _federation("codes"), "Clusters"),
|
|
158
158
|
Command(("cluster accept",), "", _federation("accept"), "Clusters", hidden=True),
|
|
159
159
|
Command(("global cluster",), "", _federation("cluster"), "Global", hidden=True),
|
|
160
160
|
# ---- gist ----
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import re
|
|
20
21
|
import sys
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
|
|
@@ -37,6 +38,10 @@ def _lab(party: dict) -> str:
|
|
|
37
38
|
return f"{party['display_name']} ({short}…)" if party.get("display_name") else party["deployment_id"]
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
def _n(count: int, noun: str) -> str:
|
|
42
|
+
return f"{count} {noun}{'' if count == 1 else 's'}"
|
|
43
|
+
|
|
44
|
+
|
|
40
45
|
def _day(epoch) -> str:
|
|
41
46
|
import datetime
|
|
42
47
|
|
|
@@ -63,6 +68,7 @@ def cmd_clusters() -> int:
|
|
|
63
68
|
for index, item in enumerate(items):
|
|
64
69
|
if index:
|
|
65
70
|
rows.append("")
|
|
71
|
+
owner_row = None
|
|
66
72
|
if item["role"] == "owner":
|
|
67
73
|
invites = item["open_invites"]
|
|
68
74
|
rows.append(f"{item['name']} (yours)" + (f" {invites} open invite{'s' if invites != 1 else ''}" if invites else ""))
|
|
@@ -71,20 +77,25 @@ def cmd_clusters() -> int:
|
|
|
71
77
|
if not item["members"] and not invites:
|
|
72
78
|
rows.append(f" nobody yet: `cluster invite {item['name']}`")
|
|
73
79
|
else:
|
|
74
|
-
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"]}
|
|
75
81
|
rows.append(f"{item['name']} {item['state']}")
|
|
76
82
|
labs = [(owner, "admin", "", None), (me, "member", "", None)] + [
|
|
77
83
|
(p, "member", p["state"] or "joining", None) for p in item.get("peers", [])]
|
|
78
84
|
for lab, role, lab_state, since in labs:
|
|
79
|
-
you =
|
|
80
|
-
line = f" {_lab(lab)} {role}
|
|
85
|
+
you = lab["deployment_id"] == me["deployment_id"]
|
|
86
|
+
line = f" {_lab(lab)} {role}" + (" (you)" if you else "")
|
|
81
87
|
if lab_state and lab_state != "active":
|
|
82
88
|
line += f" {lab_state}"
|
|
83
89
|
if since:
|
|
84
90
|
line += f" since {_day(since)}"
|
|
85
91
|
rows.append(line)
|
|
86
|
-
|
|
87
|
-
|
|
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')}")
|
|
88
99
|
rows.append(f" {item['students']} of your users enrolled, {item['visitors']} visiting")
|
|
89
100
|
print()
|
|
90
101
|
print_panel("Clusters", rows, literal=True)
|
|
@@ -106,7 +117,7 @@ def _ask_for(verb: str) -> str:
|
|
|
106
117
|
what = {"create": "Name for the new cluster (short slug, e.g. ucla-lab)",
|
|
107
118
|
"invite": "Which cluster to invite a lab into",
|
|
108
119
|
"accept": "Which cluster's pending join to accept",
|
|
109
|
-
"groups": "Which cluster
|
|
120
|
+
"groups": "Which cluster to share groups with",
|
|
110
121
|
"codes": "Which cluster to make student codes for",
|
|
111
122
|
"remove": "Which cluster to leave (revokes the contract)"}[verb]
|
|
112
123
|
if verb != "create" and names:
|
|
@@ -115,6 +126,40 @@ def _ask_for(verb: str) -> str:
|
|
|
115
126
|
return _input(f"{what}{f' [{default}]' if default else ''}: ") or default
|
|
116
127
|
|
|
117
128
|
|
|
129
|
+
def _share_groups(name: str) -> int:
|
|
130
|
+
"""Pick, by number, which of this server's groups the cluster shares.
|
|
131
|
+
Toggling: a shared group chosen again is unshared."""
|
|
132
|
+
from ...common.utils.ansi_codes import Sty
|
|
133
|
+
from ..user_group_handler import user_group_handler
|
|
134
|
+
from . import cluster
|
|
135
|
+
|
|
136
|
+
all_groups = user_group_handler.get_all_user_groups(include_users=False) or []
|
|
137
|
+
if not all_groups:
|
|
138
|
+
_print(" No user groups yet: make one with `groups create`, then share it here.", Sty.WARNING)
|
|
139
|
+
return 1
|
|
140
|
+
shared = {g["group_id"] for g in cluster.groups(name)}
|
|
141
|
+
print(f" Groups on this server (shared with {name} are marked):")
|
|
142
|
+
for index, group in enumerate(all_groups, 1):
|
|
143
|
+
mark = "*" if int(group["group_id"]) in shared else " "
|
|
144
|
+
devices = group.get("devices_whitelist") or []
|
|
145
|
+
print(f" {mark} {index:>2} {group['group_name']} ({len(devices)} device{'s' if len(devices) != 1 else ''})")
|
|
146
|
+
answer = _input("Toggle by number, e.g. 2 or 1,3 (Enter to leave as is): ")
|
|
147
|
+
picks = [part for part in re.split(r"[\s,]+", answer) if part]
|
|
148
|
+
if not picks:
|
|
149
|
+
return 0
|
|
150
|
+
for pick in picks:
|
|
151
|
+
if not pick.isdigit() or not 1 <= int(pick) <= len(all_groups):
|
|
152
|
+
_print(f" {pick} is not a number in the list.", Sty.WARNING)
|
|
153
|
+
continue
|
|
154
|
+
group = all_groups[int(pick) - 1]
|
|
155
|
+
if int(group["group_id"]) in shared:
|
|
156
|
+
_print(f" {cluster.detach_group(name, str(group['group_id']))} is no longer shared with {name}.", Sty.GRAY)
|
|
157
|
+
else:
|
|
158
|
+
_print(f" {cluster.attach_group(name, str(group['group_id']))} is now shared with {name}; "
|
|
159
|
+
"everyone in the cluster gets it.", Sty.GRAY)
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
|
|
118
163
|
def _input(prompt: str) -> str:
|
|
119
164
|
# The console's own prompt session: a bare input() inside the running
|
|
120
165
|
# server fights prompt_toolkit for the terminal and kills the process.
|
|
@@ -128,7 +173,7 @@ def _input(prompt: str) -> str:
|
|
|
128
173
|
|
|
129
174
|
|
|
130
175
|
_USAGE = ("Usage: cluster list | cluster create|invite|remove <name> | cluster join <code> | "
|
|
131
|
-
"cluster groups <name>
|
|
176
|
+
"cluster groups [<name>] | cluster codes [<name>] [count]")
|
|
132
177
|
|
|
133
178
|
|
|
134
179
|
def cmd_cluster(argv: list[str]) -> int:
|
|
@@ -140,6 +185,10 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
140
185
|
words = [a for a in argv[1:] if not a.startswith("--")]
|
|
141
186
|
if verb == "list" and not words:
|
|
142
187
|
return cmd_clusters()
|
|
188
|
+
if verb == "codes" and words and words[0].isdigit() and words[0] not in cluster.load():
|
|
189
|
+
words = [_ask_for(verb), words[0]] # `cluster codes 3`: the count came first
|
|
190
|
+
if not words[0]:
|
|
191
|
+
return 2
|
|
143
192
|
if not words and verb in ("create", "invite", "accept", "join", "remove", "groups", "codes"):
|
|
144
193
|
# Leaving the argument out asks for it (docs/deployment-groups.md §8).
|
|
145
194
|
answer = _ask_for(verb)
|
|
@@ -167,10 +216,8 @@ def cmd_cluster(argv: list[str]) -> int:
|
|
|
167
216
|
elif verb == "remove" and arg:
|
|
168
217
|
_print(f" {arg}: {cluster.remove(arg)}.", Sty.GRAY)
|
|
169
218
|
elif verb == "groups" and len(words) == 1:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
_print(f" Attach one with `cluster groups {arg} add <group>`; students of every lab in the "
|
|
173
|
-
"cluster get it. Group names stay on this server.", Sty.GRAY)
|
|
219
|
+
cluster._find(cluster.load(), arg)
|
|
220
|
+
return _share_groups(arg)
|
|
174
221
|
elif verb == "groups" and len(words) == 3 and words[1] in ("add", "remove"):
|
|
175
222
|
if words[1] == "add":
|
|
176
223
|
_print(f" {cluster.attach_group(arg, words[2])} is now shared with {arg}.", Sty.GRAY)
|
|
@@ -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)
|
|
@@ -52,7 +52,7 @@ remoteRF_server/host/host_tunnel_server.py,sha256=sz06xOtFbaDZSy1UQfw6faKhJmuOy5
|
|
|
52
52
|
remoteRF_server/server/__init__.py,sha256=-1h_xQjiE66oiWRO-vnLGJ4smpgUNhFuV-ZreW9_tD4,667
|
|
53
53
|
remoteRF_server/server/acc_perms.py,sha256=2loCvstVu8uvsb2uCv2Sk7iNQ5GyO_sBIkPQFzqOaRI,13334
|
|
54
54
|
remoteRF_server/server/cert_provider.py,sha256=CRDrZL4DSf-eL6wzPJzwucrzPLOr2PvNRq5V--oZtr0,6272
|
|
55
|
-
remoteRF_server/server/commands.py,sha256=
|
|
55
|
+
remoteRF_server/server/commands.py,sha256=xG_RAYm0nHDqN74b-QPHizw2OodfrYAvdYlVNNi7tGE,17279
|
|
56
56
|
remoteRF_server/server/console.py,sha256=J91LfwPVmBgielOxfE740NOYduekggyZkqi-47y_A4w,4472
|
|
57
57
|
remoteRF_server/server/control.py,sha256=Uq1Fk0zIoHHX_EXfz3OuNle8urvO_UhAiB5Lk5cs2e4,5177
|
|
58
58
|
remoteRF_server/server/device_manager.py,sha256=nRTvk_jeZFU3QnlM6qf3DIckU2V2e_gyDeD5gW14KQI,38802
|
|
@@ -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.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{remoterf_server-1.1.0.dev34.dist-info → remoterf_server-1.1.0.dev36.dist-info}/top_level.txt
RENAMED
|
File without changes
|