RemoteRF 0.0.7.46__tar.gz → 0.0.7.48__tar.gz

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.
Files changed (42) hide show
  1. {remoterf-0.0.7.46/src/remoteRF.egg-info → remoterf-0.0.7.48}/PKG-INFO +2 -1
  2. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/setup.py +18 -7
  3. remoterf-0.0.7.48/src/remoteRF/cli.py +141 -0
  4. remoterf-0.0.7.48/src/remoteRF/config/cert_fetcher.py +118 -0
  5. remoterf-0.0.7.48/src/remoteRF/config/config_cli.py +174 -0
  6. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/core/acc_login.py +1 -1
  7. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/core/app.py +34 -2
  8. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/core/grpc_acc.py +8 -1
  9. remoterf-0.0.7.48/src/remoteRF/core/grpc_client.py +320 -0
  10. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/core/version.py +1 -1
  11. {remoterf-0.0.7.46 → remoterf-0.0.7.48/src/remoteRF.egg-info}/PKG-INFO +2 -1
  12. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF.egg-info/SOURCES.txt +4 -7
  13. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF.egg-info/entry_points.txt +2 -0
  14. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF.egg-info/requires.txt +1 -0
  15. remoterf-0.0.7.46/src/remoteRF/core/certs/ca.crt +0 -32
  16. remoterf-0.0.7.46/src/remoteRF/core/certs/ca.key +0 -52
  17. remoterf-0.0.7.46/src/remoteRF/core/certs/cert.pem +0 -19
  18. remoterf-0.0.7.46/src/remoteRF/core/certs/key.pem +0 -28
  19. remoterf-0.0.7.46/src/remoteRF/core/certs/server.crt +0 -19
  20. remoterf-0.0.7.46/src/remoteRF/core/certs/server.key +0 -28
  21. remoterf-0.0.7.46/src/remoteRF/core/grpc_client.py +0 -100
  22. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/LICENSE +0 -0
  23. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/MANIFEST.in +0 -0
  24. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/README.md +0 -0
  25. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/setup.cfg +0 -0
  26. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/__init__.py +0 -0
  27. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/__init__.py +0 -0
  28. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/grpc/__init__.py +0 -0
  29. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
  30. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
  31. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/utils/__init__.py +0 -0
  32. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/utils/ansi_codes.py +0 -0
  33. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/utils/api_token.py +0 -0
  34. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/utils/list_string.py +0 -0
  35. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/common/utils/process_arg.py +0 -0
  36. {remoterf-0.0.7.46/src/remoteRF/core/certs → remoterf-0.0.7.48/src/remoteRF/config}/__init__.py +0 -0
  37. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/core/__init__.py +0 -0
  38. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/drivers/__init__.py +0 -0
  39. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
  40. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
  41. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF.egg-info/dependency_links.txt +0 -0
  42. {remoterf-0.0.7.46 → remoterf-0.0.7.48}/src/remoteRF.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 0.0.7.46
3
+ Version: 0.0.7.48
4
4
  Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
5
5
  Author: Ethan Ge
6
6
  Author-email: ethoGalaxy@gmail.com
@@ -14,6 +14,7 @@ Requires-Dist: protobuf<6.0.0,>=5.0.0
14
14
  Requires-Dist: numpy
15
15
  Requires-Dist: prompt_toolkit
16
16
  Requires-Dist: python-dotenv
17
+ Requires-Dist: prompt-toolkit
17
18
  Dynamic: author
18
19
  Dynamic: author-email
19
20
  Dynamic: classifier
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
6
6
 
7
7
  setup(
8
8
  name="remoterf",
9
- version="0.0.7.46",
9
+ version="0.0.7.48",
10
10
  author="Ethan Ge",
11
11
  author_email="ethoGalaxy@gmail.com",
12
12
  description="A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.",
@@ -17,7 +17,7 @@ setup(
17
17
  license_file='MIT',
18
18
  include_package_data=True, # Includes files specified in MANIFEST.in
19
19
  install_requires=[
20
- "grpcio==1.71.0", "protobuf>=5.0.0,<6.0.0", "numpy", "prompt_toolkit", "python-dotenv"
20
+ "grpcio==1.71.0", "protobuf>=5.0.0,<6.0.0", "numpy", "prompt_toolkit", "python-dotenv", "prompt-toolkit"
21
21
  ],
22
22
  classifiers=[
23
23
  "Programming Language :: Python :: 3",
@@ -25,10 +25,21 @@ setup(
25
25
  "Operating System :: OS Independent",
26
26
  ],
27
27
  python_requires='>=3.10',
28
+ # entry_points={
29
+ # 'console_scripts': [
30
+ # 'remoterf-login=remoteRF.core.acc_login:main',
31
+ # 'remoterf-v=remoteRF.core.version:main',
32
+ # 'remoterf-config=remoteRF.config.config_cli:main',
33
+ # ],
34
+ # },
35
+
28
36
  entry_points={
29
- 'console_scripts': [
30
- 'remoterf-login=remoteRF.core.acc_login:main',
31
- 'remoterf-v=remoteRF.core.version:main'
32
- ],
33
- },
37
+ "console_scripts": [
38
+ "remoterf=remoteRF.cli:main",
39
+ "remoterf-login=remoteRF.core.acc_login:main",
40
+ "remoterf-v=remoteRF.core.version:main",
41
+ "remoterf-config=remoteRF.config.config_cli:main",
42
+ ],
43
+ },
44
+
34
45
  )
@@ -0,0 +1,141 @@
1
+ # src/remoteRF/cli.py
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import sys
6
+ from typing import Optional, Sequence
7
+
8
+ from remoteRF.core.acc_login import main as login_main
9
+ from remoteRF.core.version import main as version_main
10
+ from remoteRF.config.config_cli import main as config_main
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ p = argparse.ArgumentParser(
15
+ prog="remoterf",
16
+ formatter_class=argparse.RawTextHelpFormatter,
17
+ add_help=True,
18
+ description=(
19
+ "RemoteRF unified CLI\n\n"
20
+ "Examples:\n"
21
+ " remoterf --login\n"
22
+ " remoterf -l\n"
23
+ " remoterf --config --addr 164.67.195.207:61005\n"
24
+ " remoterf -c -a 164.67.195.207:61005\n"
25
+ " remoterf --config --wipe\n"
26
+ " remoterf -v\n"
27
+ ),
28
+ )
29
+
30
+ mode = p.add_mutually_exclusive_group()
31
+
32
+ mode.add_argument("-l", "--login", action="store_true", help="Login / register (interactive).")
33
+ mode.add_argument("-c", "--config", action="store_true", help="Configure server target / CA cert.")
34
+ mode.add_argument("-v", "--version", action="store_true", help="Print version and exit.")
35
+
36
+ # These are only meaningful with --config, but we accept them at top-level
37
+ # so your UX works as requested.
38
+ p.add_argument(
39
+ "-a", "--addr", "-addr",
40
+ dest="addr",
41
+ default=None,
42
+ help="Server address (host:port). Used with --config / -c.",
43
+ )
44
+
45
+ p.add_argument(
46
+ "-w", "--wipe",
47
+ action="store_true",
48
+ help="Wipe local config (~/.config/remoterf). Used with --config / -c.",
49
+ )
50
+
51
+ p.add_argument(
52
+ "--yes",
53
+ action="store_true",
54
+ help="Skip confirmation prompts (use with --config --wipe).",
55
+ )
56
+
57
+ return p
58
+
59
+
60
+ def main(argv: Optional[Sequence[str]] = None) -> int:
61
+ argv = list(sys.argv[1:] if argv is None else argv)
62
+
63
+ parser = build_parser()
64
+
65
+ # No args => help panel
66
+ if len(argv) == 0:
67
+ parser.print_help()
68
+ return 0
69
+
70
+ args, extra = parser.parse_known_args(argv)
71
+
72
+ # --- Version ---
73
+ if args.version:
74
+ if extra:
75
+ parser.error(f"Unexpected arguments for --version: {' '.join(extra)}")
76
+ # If your version_main supports argv, pass [].
77
+ try:
78
+ version_main([])
79
+ except TypeError:
80
+ version_main()
81
+ return 0
82
+
83
+ # --- Login ---
84
+ if args.login:
85
+ if extra:
86
+ parser.error(f"Unexpected arguments for --login: {' '.join(extra)}")
87
+ try:
88
+ login_main([])
89
+ except TypeError:
90
+ login_main()
91
+ return 0
92
+
93
+ # --- Config ---
94
+ if args.config:
95
+ cfg_argv: list[str] = []
96
+
97
+ # Normalize to whatever config_cli expects.
98
+ # If your config_cli supports --wipe/--yes, pass them through.
99
+ if args.wipe:
100
+ cfg_argv.append("--wipe")
101
+ if args.yes:
102
+ cfg_argv.append("--yes")
103
+
104
+ # Address handling
105
+ if args.addr:
106
+ # If config_cli expects a positional addr, pass it positionally:
107
+ cfg_argv.append(args.addr)
108
+ # If your config_cli instead expects --addr, switch to:
109
+ # cfg_argv += ["--addr", args.addr]
110
+
111
+ # Preserve any extra args for config_cli (if you want)
112
+ cfg_argv += extra
113
+
114
+ # If neither addr nor wipe was supplied, show config help-like message.
115
+ if not args.addr and not args.wipe and len(extra) == 0:
116
+ # Call config_cli with --help if it supports it; otherwise print ours.
117
+ try:
118
+ config_main(["--help"])
119
+ except Exception:
120
+ parser.error("Config mode requires --addr <host:port> or --wipe.")
121
+ return 0
122
+
123
+ try:
124
+ config_main(cfg_argv)
125
+ except TypeError:
126
+ # Fallback if config_main does not accept argv; last resort:
127
+ old = sys.argv[:]
128
+ try:
129
+ sys.argv = ["remoterf-config", *cfg_argv]
130
+ config_main()
131
+ finally:
132
+ sys.argv = old
133
+ return 0
134
+
135
+ # If user passed only -a/-w without -c/--config, show help
136
+ parser.print_help()
137
+ return 0
138
+
139
+
140
+ if __name__ == "__main__":
141
+ raise SystemExit(main())
@@ -0,0 +1,118 @@
1
+ # cert_fetcher.py
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import os
7
+ import socket
8
+ import urllib.request
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+
13
+ CERT_FILENAME_DEFAULT = "ca.crt"
14
+
15
+
16
+ def _default_config_dir() -> Path:
17
+ # Cross-platform-ish default; adjust if you already have a standard in your project.
18
+ # Linux: ~/.config/remoterf
19
+ # macOS: also acceptable; if you want Apple-standard, use ~/Library/Application Support/remoterf
20
+ base = Path(os.path.expanduser("~")) / ".config" / "remoterf"
21
+ return base
22
+
23
+
24
+ def _ensure_parent_dir(p: Path) -> None:
25
+ p.parent.mkdir(parents=True, exist_ok=True)
26
+
27
+
28
+ def _looks_like_pem_cert(data: bytes) -> bool:
29
+ return b"BEGIN CERTIFICATE" in data and b"END CERTIFICATE" in data
30
+
31
+
32
+ def sha256_fingerprint_pem(pem_bytes: bytes) -> str:
33
+ h = hashlib.sha256(pem_bytes).hexdigest()
34
+ return ":".join(h[i:i+2] for i in range(0, len(h), 2))
35
+
36
+
37
+ def _fetch_http(host: str, port: int, timeout_sec: float) -> bytes:
38
+ url = f"http://{host}:{port}/ca.crt"
39
+ req = urllib.request.Request(url, method="GET")
40
+ with urllib.request.urlopen(req, timeout=timeout_sec) as resp:
41
+ data = resp.read()
42
+ return data
43
+
44
+
45
+ def _fetch_raw_tcp(host: str, port: int, timeout_sec: float) -> bytes:
46
+ chunks: list[bytes] = []
47
+ with socket.create_connection((host, port), timeout=timeout_sec) as s:
48
+ s.settimeout(timeout_sec)
49
+ while True:
50
+ try:
51
+ b = s.recv(4096)
52
+ except socket.timeout:
53
+ break
54
+ if not b:
55
+ break
56
+ chunks.append(b)
57
+ return b"".join(chunks)
58
+
59
+
60
+ def fetch_and_save_ca_cert(
61
+ host: str,
62
+ port: int,
63
+ *,
64
+ out_path: Optional[str | Path] = None,
65
+ profile: Optional[str] = None,
66
+ timeout_sec: float = 3.0,
67
+ overwrite: bool = True,
68
+ ) -> bool:
69
+ """
70
+ Fetch CA cert from the server bootstrap endpoint and save it to disk.
71
+
72
+ Args:
73
+ host: server host/ip running cert_provider
74
+ port: cert_provider port (NOT the TLS gRPC port)
75
+ out_path: explicit output path (overrides profile/default location)
76
+ profile: if provided, saves as ~/.config/remoterf/certs/<profile>.crt
77
+ timeout_sec: network timeout
78
+ overwrite: whether to overwrite existing file
79
+
80
+ Returns:
81
+ True on success, False on any failure.
82
+ """
83
+ try:
84
+ if not isinstance(port, int):
85
+ port = int(port)
86
+
87
+ # Determine destination path
88
+ if out_path is not None:
89
+ dest = Path(out_path).expanduser().resolve()
90
+ else:
91
+ cfg = _default_config_dir()
92
+ certs_dir = cfg / "certs"
93
+ name = f"{profile}.crt" if profile else CERT_FILENAME_DEFAULT
94
+ dest = certs_dir / name
95
+
96
+ _ensure_parent_dir(dest)
97
+
98
+ if dest.exists() and not overwrite:
99
+ return True # already present, treat as success
100
+
101
+ # Fetch (HTTP first, then raw TCP fallback)
102
+ data = b""
103
+ try:
104
+ data = _fetch_http(host, port, timeout_sec)
105
+ except Exception:
106
+ data = _fetch_raw_tcp(host, port, timeout_sec)
107
+
108
+ if not data or not _looks_like_pem_cert(data):
109
+ return False
110
+
111
+ # Save
112
+ dest.write_bytes(data)
113
+
114
+ # Optional: you may want to return/print fingerprint, but requested API is bool.
115
+ return True
116
+
117
+ except Exception:
118
+ return False
@@ -0,0 +1,174 @@
1
+ # src/remoteRF/core/remoterf_config.py
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ import shutil
8
+ from pathlib import Path
9
+ from typing import Tuple
10
+
11
+ from .cert_fetcher import fetch_and_save_ca_cert
12
+
13
+ # -----------------------------
14
+ # Local config locations
15
+ # -----------------------------
16
+ def _config_root() -> Path:
17
+ return Path(os.path.expanduser("~")) / ".config" / "remoterf"
18
+
19
+ def _env_path() -> Path:
20
+ return _config_root() / ".env"
21
+
22
+ def _certs_dir() -> Path:
23
+ return _config_root() / "certs"
24
+
25
+ def _parse_hostport(s: str) -> Tuple[str, int]:
26
+ s = s.strip()
27
+ if "://" in s:
28
+ s = s.split("://", 1)[1]
29
+
30
+ if ":" not in s:
31
+ raise ValueError("Expected format host:port")
32
+
33
+ host, port_str = s.rsplit(":", 1)
34
+ host = host.strip()
35
+ port = int(port_str.strip())
36
+ if not host:
37
+ raise ValueError("Host is empty")
38
+ if port <= 0 or port > 65535:
39
+ raise ValueError("Port out of range")
40
+ return host, port
41
+
42
+ def _write_env_kv(path: Path, kv: dict[str, str]) -> None:
43
+ path.parent.mkdir(parents=True, exist_ok=True)
44
+ lines: list[str] = []
45
+ for k, v in kv.items():
46
+ if any(c.isspace() for c in v) or any(c in v for c in ['"', "'"]):
47
+ v = v.replace('"', '\\"')
48
+ lines.append(f'{k}="{v}"')
49
+ else:
50
+ lines.append(f"{k}={v}")
51
+ path.write_text("\n".join(lines) + "\n", encoding="utf-8")
52
+
53
+ def _confirm_wipe(root: Path) -> bool:
54
+ prompt = (
55
+ f"This will permanently delete ALL RemoteRF config at:\n"
56
+ f" {root}\n\n"
57
+ f"Type 'wipe' to confirm: "
58
+ )
59
+ try:
60
+ return input(prompt).strip().lower() == "wipe"
61
+ except KeyboardInterrupt:
62
+ print("\nCancelled.")
63
+ return False
64
+
65
+ def _wipe_config(root: Path) -> None:
66
+ if not root.exists():
67
+ print(f"No config found at: {root}")
68
+ return
69
+ if not root.is_dir():
70
+ raise RuntimeError(f"Config root exists but is not a directory: {root}")
71
+ shutil.rmtree(root)
72
+ print(f"Wiped RemoteRF config: {root}")
73
+
74
+ def main(argv=None) -> None:
75
+ import argparse
76
+
77
+ parser = argparse.ArgumentParser(
78
+ prog="remoterf-config",
79
+ add_help=True,
80
+ formatter_class=argparse.RawTextHelpFormatter,
81
+ description=(
82
+ "Configure RemoteRF to talk to a server.\n"
83
+ "Fetches the server CA certificate and saves the gRPC target locally."
84
+ ),
85
+ )
86
+
87
+ parser.add_argument(
88
+ "addr",
89
+ nargs="?",
90
+ help="Server address in host:port form. Example: 164.67.195.207:61005",
91
+ )
92
+
93
+ parser.add_argument(
94
+ "--wipe",
95
+ action="store_true",
96
+ help="Delete ALL existing RemoteRF config at ~/.config/remoterf and exit.",
97
+ )
98
+
99
+ parser.add_argument(
100
+ "--yes",
101
+ action="store_true",
102
+ help="Skip confirmation prompts (use with --wipe).",
103
+ )
104
+
105
+ args = parser.parse_args()
106
+
107
+ # ----- Wipe mode -----
108
+ if args.wipe:
109
+ root = _config_root()
110
+ if not args.yes:
111
+ if not _confirm_wipe(root):
112
+ print("Wipe aborted.")
113
+ sys.exit(1)
114
+ try:
115
+ _wipe_config(root)
116
+ sys.exit(0)
117
+ except Exception as e:
118
+ print(f"Error wiping config: {e}", file=sys.stderr)
119
+ sys.exit(1)
120
+
121
+ # ----- Normal configure mode -----
122
+ if not args.addr:
123
+ print(
124
+ "\nError: missing required argument: host:port\n\n"
125
+ "Usage:\n"
126
+ " remoterf-config <host:port>\n"
127
+ " remoterf-config --wipe [--yes]\n\n"
128
+ "Example:\n"
129
+ " remoterf-config 164.67.195.207:61005\n"
130
+ )
131
+ sys.exit(2)
132
+
133
+ try:
134
+ host, grpc_port = _parse_hostport(args.addr)
135
+ except Exception as e:
136
+ print(f"Error: invalid addr '{args.addr}': {e}", file=sys.stderr)
137
+ sys.exit(2)
138
+
139
+ cert_port = grpc_port + 1
140
+ profile = "default"
141
+ timeout_sec = 3.0
142
+ overwrite = True
143
+
144
+ certs_dir = _certs_dir()
145
+ certs_dir.mkdir(parents=True, exist_ok=True)
146
+ ca_out = certs_dir / f"{profile}.crt"
147
+
148
+ fetched_ok = fetch_and_save_ca_cert(
149
+ host,
150
+ cert_port,
151
+ out_path=ca_out,
152
+ timeout_sec=timeout_sec,
153
+ overwrite=overwrite,
154
+ )
155
+
156
+ if not fetched_ok:
157
+ print(f"Failed to fetch CA cert from {host}:{cert_port}.", file=sys.stderr)
158
+ sys.exit(1)
159
+
160
+ env_file = _env_path()
161
+ _write_env_kv(env_file, {
162
+ "REMOTERF_ADDR": f"{host}:{grpc_port}",
163
+ "REMOTERF_CA_CERT": str(ca_out),
164
+ "REMOTERF_PROFILE": profile,
165
+ })
166
+
167
+ print("RemoteRF configured successfully.")
168
+ print(f" gRPC target : {host}:{grpc_port}")
169
+ print(f" cert port : {host}:{cert_port}")
170
+ print(f" CA cert : {ca_out}")
171
+ print(f" env file : {env_file}")
172
+
173
+ if __name__ == "__main__":
174
+ main()
@@ -1,4 +1,4 @@
1
1
  from . import app as client
2
2
 
3
- def main():
3
+ def main(argv=None):
4
4
  pass
@@ -1,3 +1,4 @@
1
+ from remoteRF.core.grpc_client import handle_admin_command
1
2
  from . import *
2
3
  from ..common.utils import *
3
4
 
@@ -30,6 +31,7 @@ def welcome():
30
31
 
31
32
  account.password = password
32
33
  account.email = input("Email: ") # TODO: Email verification.
34
+ account.enrollment_code = input("Enrollment Code: ")
33
35
  # check if login was valid
34
36
  os.system('cls' if os.name == 'nt' else 'clear')
35
37
 
@@ -59,6 +61,7 @@ def commands():
59
61
  printf("'getdev' ", Sty.MAGENTA, " : View devices", Sty.DEFAULT)
60
62
  printf("'help' or 'h' ", Sty.MAGENTA, " : Show this help message", Sty.DEFAULT)
61
63
  printf("'perms' ", Sty.MAGENTA, " : View permissions", Sty.DEFAULT)
64
+ printf("'enroll' ", Sty.MAGENTA, " : Enroll with an enrollment code", Sty.DEFAULT)
62
65
  printf("'exit' or 'quit' ", Sty.MAGENTA, ": Exit", Sty.DEFAULT)
63
66
  printf("'getres' ", Sty.MAGENTA, " : View all reservations", Sty.DEFAULT)
64
67
  printf("'myres' ", Sty.MAGENTA, " : View my reservations", Sty.DEFAULT)
@@ -66,8 +69,20 @@ def commands():
66
69
  printf("'resdev' ", Sty.MAGENTA, " : Reserve a device", Sty.DEFAULT)
67
70
  # printf("'resdev -n' ", Sty.MAGENTA, "- naive reserve device", Sty.DEFAULT)
68
71
  # printf("'resdev s' ", Sty.MAGENTA, "- Reserve a Device (by single date)", Sty.DEFAULT)
69
- # check if user is admin
70
- # if account.get_perms().results['UC'] == 'Admin':
72
+
73
+ if account.is_admin:
74
+ print()
75
+ printf("Admin Commands:", Sty.BOLD)
76
+ printf("'admin printa' ", Sty.MAGENTA, " : Print all accounts", Sty.DEFAULT)
77
+ printf("'admin printr' ", Sty.MAGENTA, " : Print all reservations", Sty.DEFAULT)
78
+ printf("'admin printp' ", Sty.MAGENTA, " : Print all perms", Sty.DEFAULT)
79
+ printf("'admin printd' ", Sty.MAGENTA, " : Print all devices", Sty.DEFAULT)
80
+ printf("'admin rm a <username>' ", Sty.MAGENTA, " : Remove one account", Sty.DEFAULT)
81
+ printf("'admin rm aa' ", Sty.MAGENTA, " : Remove all accounts", Sty.DEFAULT)
82
+ printf("'admin rm ar' ", Sty.MAGENTA, " : Remove all reservations", Sty.DEFAULT)
83
+ # If you expose set_account remotely:
84
+ printf("'admin setacc <username> <U|P|A> [args...]' ", Sty.MAGENTA, " : Set perms", Sty.DEFAULT)
85
+
71
86
 
72
87
  def clear():
73
88
  os.system('cls' if os.name == 'nt' else 'clear')
@@ -267,6 +282,18 @@ def perms():
267
282
  printf(f'No restrictions on reservation count or duration.', Sty.DEFAULT)
268
283
  else:
269
284
  printf(f"Error: Unknown permission level {results[0]}", Sty.BRIGHT_RED)
285
+
286
+ def enroll():
287
+ code = session.prompt(stylize("Enter your enrollment code: ", Sty.DEFAULT))
288
+ account.enrollment_code = code
289
+ data = account.set_enroll()
290
+
291
+ if 'ace' in data.results:
292
+ print(f"Error: {unmap_arg(data.results['ace'])}")
293
+ return
294
+ else:
295
+ # todo: print out specifics, like group name (ie: successfully enrolled in ECE 132A)
296
+ printf("Enrollment successful.", Sty.BOLD_GREEN)
270
297
 
271
298
  # New block scheduling
272
299
 
@@ -484,6 +511,8 @@ while True:
484
511
  commands()
485
512
  elif inpu == "perms":
486
513
  perms()
514
+ elif inpu == "enroll":
515
+ enroll()
487
516
  elif inpu == "quit" or inpu == "exit":
488
517
  break
489
518
  elif inpu == "getres":
@@ -496,10 +525,13 @@ while True:
496
525
  interactive_reserve_next_days(block_minutes=30)
497
526
  elif inpu == 'cancelres':
498
527
  cancel_my_reservation()
528
+
499
529
  elif inpu == 'resdev -n':
500
530
  # check if user is admin
501
531
  # if account.get_perms().results['UC'] == 'Admin':
502
532
  reserve()
533
+ elif account.is_admin and inpu.strip().startswith("admin"):
534
+ handle_admin_command(inpu)
503
535
  else:
504
536
  print(f"Unknown command: {inpu}")
505
537
  except KeyboardInterrupt:
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  from .grpc_client import rpc_client
2
3
  from ..common.utils import *
3
4
 
@@ -8,9 +9,10 @@ class RemoteRFAccount:
8
9
  self.username = username
9
10
  self.password = password
10
11
  self.email = email
12
+ self.enrollment_code = ""
11
13
 
12
14
  def create_user(self):
13
- response = rpc_client(function_name="ACC:create_user", args={"un":map_arg(self.username), "pw":map_arg(self.password), "em":map_arg(self.email)})
15
+ response = rpc_client(function_name="ACC:create_user", args={"un":map_arg(self.username), "pw":map_arg(self.password), "em":map_arg(self.email), "ec":map_arg(self.enrollment_code)})
14
16
  if 'UC' in response.results:
15
17
  print(f'User {unmap_arg(response.results["UC"])} successfully created.')
16
18
  return True
@@ -24,6 +26,8 @@ class RemoteRFAccount:
24
26
  response = rpc_client(function_name="ACC:login", args={"un":map_arg(username), "pw":map_arg(password)})
25
27
  if 'UC' in response.results:
26
28
  print(f'User {unmap_arg(response.results["UC"])} successful login.')
29
+
30
+ self.is_admin = (ast.literal_eval(unmap_arg(self.get_perms().results['UC']))[0][0] == 'Admin')
27
31
  return True
28
32
  elif 'UE' in response.results:
29
33
  print(f'Error: {unmap_arg(response.results["UE"])}')
@@ -49,4 +53,7 @@ class RemoteRFAccount:
49
53
  def get_perms(self):
50
54
  return rpc_client(function_name='ACC:get_perms', args={"un":map_arg(self.username), "pw":map_arg(self.password)})
51
55
 
56
+ def set_enroll(self):
57
+ return rpc_client(function_name='ACC:set_enroll', args={"un":map_arg(self.username), "pw":map_arg(self.password), "ec":map_arg(self.enrollment_code)})
58
+
52
59