remoteRF-server 1.7.12__tar.gz → 1.7.13__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.
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/PKG-INFO +1 -32
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/README.md +0 -31
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/pyproject.toml +1 -1
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/remote_admin_shell.py +3 -3
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/commands.py +0 -5
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/global_onboarding.py +83 -25
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/grpc_server.py +0 -4
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/reservation.py +40 -166
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/serverrf_cli.py +0 -327
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/PKG-INFO +1 -32
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/SOURCES.txt +0 -3
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_client_sessions.py +100 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_global_onboarding.py +120 -6
- remoterf_server-1.7.12/src/remoteRF_server/tools/gist_status.py +0 -218
- remoterf_server-1.7.12/src/remoteRF_server/tools/gist_status_testing.py +0 -82
- remoterf_server-1.7.12/tests/test_status_push.py +0 -20
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/LICENSE +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/setup.cfg +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/federation.proto +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/federation_pb2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/federation_pb2_grpc.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/idl/v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/panel.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/pluto_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/adalm_pluto/poe.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/bluetooth_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/gps_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/hackrf/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/hackrf/hackrf_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/rtl_sdr/rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/ti_mmwave/ti_mmwave_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/policy.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/profiles.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/usrp_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp/usrp_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp_phaser/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/usrp_phaser/usrp_phaser_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/wifi/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/drivers/wifi/wifi_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/console.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/control.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/deployment/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/deployment/capabilities.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/device_manager.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/direct_path.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/dynamic_v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/dynamic_v2_bridge.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/admin.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/cli.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/cluster.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/contracts.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/destination.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/devices.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/enrollment.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/global_home.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/home.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/home_devices.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/identity.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/legacy_state.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/links.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/migrations.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/peers.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/permissions.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/principals.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/runtime.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/federation/trust.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/host_direct.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/host_dynamic_v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/json_output.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/remote_admin.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/remote_admin_ssh.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/schema_registry.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/startup_check.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/v2_errors.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server.egg-info/top_level.txt +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/rssi/__init__.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/rssi/cli.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/rssi/dongle.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/rssi/safety.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/rssi/scanner.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_account_hardening.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_account_names.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_account_removal.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_activity_log.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_availability.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_bind_host.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_cert_provider.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_cluster.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_commands.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_console.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_contrib_drivers.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_default_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_default_usrp_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_default_wifi_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_deployment_capabilities.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_detached_device_rpc.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_device_core_aliases.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_device_handles.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_device_init.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_devices_add_wizard.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_devices_list.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_diag.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_direct_fixed_port.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_direct_path_server.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_dynamic_v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_federated_direct_path.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_federated_dynamic_v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_federation_edges.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_first_run.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_d_contracts.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_e_federation.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_e_proto.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_f_enrollment.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_g1_logging.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_g_permissions.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_h_devices.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_gen_certs.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_global_accounts_migration.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_global_home.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_global_v1_disposal.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_hackrf_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_home_device_plane.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_host_direct.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_host_directory_store.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_host_dynamic_v2.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_idl_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_json_commands.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_lab_logins.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_panel.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_peer_transport.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_pluto_compat.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_pluto_poe.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_process_arg.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_proxy_lifecycle.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_remote_admin.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_reservation_cancel.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_reservation_handoff.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_reservation_policy_b.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_reservation_timestamps.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_rpc_manager_batch.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_rpc_manager_calln.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_rpc_manager_host_legacy.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_rpc_manager_rx_n.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_rtl_sdr_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_schema_registry.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_soak_federation.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_startup_check.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_ti_mmwave_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_ti_mmwave_schema.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_user_group_cli.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_b205mini_uhd410_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_client_server_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_client_server_localhost.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_n210_client_server_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_preload.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_profiles.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_usrp_uhd410_hardware.py +0 -0
- {remoterf_server-1.7.12 → remoterf_server-1.7.13}/tests/test_wipe.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.13
|
|
4
4
|
Summary: RemoteRF server-side control package
|
|
5
5
|
License: GPL-3.0-or-later
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -953,37 +953,6 @@ serverrf --host --delete host-name
|
|
|
953
953
|
serverrf --host --wipe
|
|
954
954
|
```
|
|
955
955
|
|
|
956
|
-
## Status (Optional)
|
|
957
|
-
|
|
958
|
-
If you desire to pull the server information (ie: live updates of reservation, server usage parameters, etc.)
|
|
959
|
-
|
|
960
|
-
It uses GitHub Gists to push the status updates.
|
|
961
|
-
|
|
962
|
-
Create public gist on your github. Keep note of the file name: (example)
|
|
963
|
-
https://gist.github.com/ethange1/2a35e08a90bf88a70dfe7f42a55685ed
|
|
964
|
-
|
|
965
|
-
The last one is the "GIST_ID" part of the URL, e.g. "2a35e08a90bf88a70dfe7f42a55685ed"
|
|
966
|
-
|
|
967
|
-
Creating the GitHub PAT Token
|
|
968
|
-
|
|
969
|
-
* GitHub → Settings
|
|
970
|
-
* Developer settings
|
|
971
|
-
* Personal access tokens → Fine-grained tokens
|
|
972
|
-
* Generate new token
|
|
973
|
-
* Set repository access to "Public Respositories"
|
|
974
|
-
* Permissions: enable Gists: Read and write
|
|
975
|
-
* Generate + copy the token (you only see it once)
|
|
976
|
-
|
|
977
|
-
```bash
|
|
978
|
-
serverrf --gist --set --id <gist_id> --file <filename>
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
Run the server normally, and you should see your gist change every minute!
|
|
982
|
-
|
|
983
|
-
```bash
|
|
984
|
-
serverrf -s
|
|
985
|
-
```
|
|
986
|
-
|
|
987
956
|
<!-- todo: -->
|
|
988
957
|
<!-- Adding plutos via the CLI -->
|
|
989
958
|
<!-- Testing functionality (disregarding Host pipeline) -->
|
|
@@ -920,37 +920,6 @@ serverrf --host --delete host-name
|
|
|
920
920
|
serverrf --host --wipe
|
|
921
921
|
```
|
|
922
922
|
|
|
923
|
-
## Status (Optional)
|
|
924
|
-
|
|
925
|
-
If you desire to pull the server information (ie: live updates of reservation, server usage parameters, etc.)
|
|
926
|
-
|
|
927
|
-
It uses GitHub Gists to push the status updates.
|
|
928
|
-
|
|
929
|
-
Create public gist on your github. Keep note of the file name: (example)
|
|
930
|
-
https://gist.github.com/ethange1/2a35e08a90bf88a70dfe7f42a55685ed
|
|
931
|
-
|
|
932
|
-
The last one is the "GIST_ID" part of the URL, e.g. "2a35e08a90bf88a70dfe7f42a55685ed"
|
|
933
|
-
|
|
934
|
-
Creating the GitHub PAT Token
|
|
935
|
-
|
|
936
|
-
* GitHub → Settings
|
|
937
|
-
* Developer settings
|
|
938
|
-
* Personal access tokens → Fine-grained tokens
|
|
939
|
-
* Generate new token
|
|
940
|
-
* Set repository access to "Public Respositories"
|
|
941
|
-
* Permissions: enable Gists: Read and write
|
|
942
|
-
* Generate + copy the token (you only see it once)
|
|
943
|
-
|
|
944
|
-
```bash
|
|
945
|
-
serverrf --gist --set --id <gist_id> --file <filename>
|
|
946
|
-
```
|
|
947
|
-
|
|
948
|
-
Run the server normally, and you should see your gist change every minute!
|
|
949
|
-
|
|
950
|
-
```bash
|
|
951
|
-
serverrf -s
|
|
952
|
-
```
|
|
953
|
-
|
|
954
923
|
<!-- todo: -->
|
|
955
924
|
<!-- Adding plutos via the CLI -->
|
|
956
925
|
<!-- Testing functionality (disregarding Host pipeline) -->
|
|
@@ -47,7 +47,7 @@ TIERS = {
|
|
|
47
47
|
"help": READ, "status": READ, "devices list": READ, "users list": READ, "users perms": READ,
|
|
48
48
|
"reservations list": READ, "groups list": READ, "codes list": READ, "hosts status": READ,
|
|
49
49
|
"hosts tokens": READ, "certs list": READ, "config list": READ, "global status": READ,
|
|
50
|
-
"cluster list": READ, "cluster info": READ, "
|
|
50
|
+
"cluster list": READ, "cluster info": READ, "users prune-proxies": READ,
|
|
51
51
|
"cluster codes list": READ, "schema browse": READ,
|
|
52
52
|
# write
|
|
53
53
|
"devices add": WRITE, "devices remove": WRITE, "devices rename": WRITE, "groups create": WRITE,
|
|
@@ -63,7 +63,7 @@ TIERS = {
|
|
|
63
63
|
"groups remove": DESTRUCTIVE, "devices wipe": DESTRUCTIVE, "reservations purge": DESTRUCTIVE,
|
|
64
64
|
"stats purge": DESTRUCTIVE, "hosts wipe": DESTRUCTIVE, "hosts forget": DESTRUCTIVE,
|
|
65
65
|
"certs reset": DESTRUCTIVE, "certs wipe": DESTRUCTIVE, "config ports": DESTRUCTIVE,
|
|
66
|
-
"cluster remove": DESTRUCTIVE,
|
|
66
|
+
"cluster remove": DESTRUCTIVE,
|
|
67
67
|
# never remote: they cut remote access, nuke everything, or pin admins
|
|
68
68
|
"stop": NEVER, "detach": NEVER, "clear": NEVER, "wipe": NEVER, "users purge": NEVER, "db purge": NEVER,
|
|
69
69
|
"global cluster": NEVER, # an old alias for every `cluster` verb, remove included: use `cluster …`
|
|
@@ -74,7 +74,7 @@ TIERS = {
|
|
|
74
74
|
}
|
|
75
75
|
DENY_FLAGS = {"--secrets"}
|
|
76
76
|
# Destructive commands whose own confirmation -y answers (the step-up asked already).
|
|
77
|
-
TAKES_YES = {"devices wipe", "stats purge", "hosts wipe", "certs wipe", "
|
|
77
|
+
TAKES_YES = {"devices wipe", "stats purge", "hosts wipe", "certs wipe", "users prune-proxies",
|
|
78
78
|
"hosts tokens", "certs reset"}
|
|
79
79
|
KEEP_BACKUPS = 10
|
|
80
80
|
PAIR_CODE = re.compile(r"[A-Z2-7]{4}-[A-Z2-7]{4}-[A-Z2-7]{4}-[A-Z2-7]{4}")
|
|
@@ -178,10 +178,6 @@ def _table() -> list[Command]:
|
|
|
178
178
|
Command(("cluster peers",), "Partner listeners this deployment dials directly: cluster peers [add <id> <host:port> --ca <ca.crt> | remove <id>]", _federation("peers"), "Clusters"),
|
|
179
179
|
Command(("cluster accept",), "", _federation("accept"), "Clusters", hidden=True),
|
|
180
180
|
Command(("global cluster",), "", _federation("cluster"), "Global", hidden=True),
|
|
181
|
-
# ---- gist ----
|
|
182
|
-
Command(("gist list",), "Show gist status settings [--secrets]", _shell("cmd_gist_show"), "Gist status"),
|
|
183
|
-
Command(("gist set",), "gist set <gist_id> <file> [--token-stdin]", _shell("cmd_gist_set"), "Gist status"),
|
|
184
|
-
Command(("gist wipe",), "Forget the gist settings [-y]", _shell("cmd_gist_wipe"), "Gist status"),
|
|
185
181
|
# ---- database ----
|
|
186
182
|
Command(("db export",), "Archive DBs, devices, keys, certs to ~/Downloads [dir]", _shell("cmd_db_export"), "Database"),
|
|
187
183
|
Command(("stats purge",), "Delete the activity log (reservation stats) [-y]", _daemon("cmd_stats_purge"), "Database", where=D),
|
|
@@ -244,7 +240,6 @@ _SECTION_BLURB = {
|
|
|
244
240
|
"Global": "public address and relay",
|
|
245
241
|
"Clusters": "sharing devices with other deployments",
|
|
246
242
|
"Schemas": "driver schemas approved on RemoteRF Global",
|
|
247
|
-
"Gist status": "publishing status to a GitHub gist",
|
|
248
243
|
"Database": "export, or wipe everything",
|
|
249
244
|
"Remote admin": "admins who reach this console from anywhere",
|
|
250
245
|
}
|
{remoterf_server-1.7.12 → remoterf_server-1.7.13}/src/remoteRF_server/server/global_onboarding.py
RENAMED
|
@@ -62,11 +62,14 @@ Once an hour it sends Global a short note: this deployment's name and
|
|
|
62
62
|
ID, version, uptime, how many accounts (and how many visit from partner
|
|
63
63
|
deployments), what devices it has, how many reservations are active, how many
|
|
64
64
|
reservations have been made and the total time reserved so far (two numbers),
|
|
65
|
-
and your LAN address.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
and your LAN address. For the devices you share on Global it also sends how
|
|
66
|
+
many reservations start in each hour of the day and on each day of the week
|
|
67
|
+
(UTC), and the reservations and time reserved per device type (counts only).
|
|
68
|
+
Nothing else leaves your server -- no credentials, no user or reservation
|
|
69
|
+
details -- and the connection only goes outward. Global publishes the network
|
|
70
|
+
totals, these breakdowns, and each deployment's name, version, devices, when
|
|
71
|
+
it joined and was last online, and how many reservations it has had and the
|
|
72
|
+
time reserved, at https://{domain}/stats.json.
|
|
70
73
|
|
|
71
74
|
A RemoteRF admin will review your new deployment before its address goes live.
|
|
72
75
|
If you opt out, you cannot opt back in without a full data reset. You may leave
|
|
@@ -737,6 +740,7 @@ STATUS_FIELDS = frozenset(
|
|
|
737
740
|
"active_reservations",
|
|
738
741
|
"reservations",
|
|
739
742
|
"reserved_s",
|
|
743
|
+
"usage",
|
|
740
744
|
"lan_address",
|
|
741
745
|
"cert_sha256",
|
|
742
746
|
}
|
|
@@ -758,23 +762,24 @@ def _db_dir() -> Path:
|
|
|
758
762
|
return cfg_dir() / "db"
|
|
759
763
|
|
|
760
764
|
|
|
761
|
-
def
|
|
765
|
+
def _rows(database: Path, sql: str, params=()) -> list:
|
|
762
766
|
import sqlite3
|
|
763
767
|
|
|
764
768
|
if not database.exists():
|
|
765
|
-
return
|
|
769
|
+
return []
|
|
766
770
|
try:
|
|
767
771
|
with sqlite3.connect(f"file:{database}?mode=ro", uri=True, timeout=2.0) as db:
|
|
768
|
-
return
|
|
772
|
+
return db.execute(sql, params).fetchall()
|
|
769
773
|
except sqlite3.Error:
|
|
770
|
-
return
|
|
774
|
+
return []
|
|
771
775
|
|
|
772
776
|
|
|
773
|
-
def
|
|
774
|
-
|
|
777
|
+
def _count(database: Path, sql: str, params=()) -> int:
|
|
778
|
+
rows = _rows(database, sql, params)
|
|
779
|
+
return int(rows[0][0]) if rows else 0
|
|
775
780
|
|
|
776
|
-
|
|
777
|
-
|
|
781
|
+
|
|
782
|
+
def _registered_devices() -> list:
|
|
778
783
|
import yaml
|
|
779
784
|
|
|
780
785
|
path = cfg_dir() / "devices.yml"
|
|
@@ -783,23 +788,74 @@ def device_summary() -> list:
|
|
|
783
788
|
devices = data.get("devices") or []
|
|
784
789
|
except (OSError, yaml.YAMLError):
|
|
785
790
|
return []
|
|
791
|
+
return [item for item in devices if type(item) is dict and "device_id" in item]
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def _device_type(item: dict) -> str:
|
|
795
|
+
return str(item.get("device_type") or "unknown").lower()
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _device_types() -> dict:
|
|
799
|
+
"""Device id -> type: devices.yml entries, then the Host directory `serverrf status` lists.
|
|
800
|
+
|
|
801
|
+
Types are bounded the way Global accepts them: a Host names its own kinds.
|
|
802
|
+
"""
|
|
803
|
+
types = {int(item["device_id"]): _device_type(item) for item in _registered_devices()}
|
|
804
|
+
try:
|
|
805
|
+
from ..host import host_tunnel_server as hts
|
|
806
|
+
|
|
807
|
+
directory = hts.get_tunnel_registry().device_directory_cached(ttl_ms=0)
|
|
808
|
+
except Exception: # noqa: BLE001 - drivers are optional at report time
|
|
809
|
+
directory = {}
|
|
810
|
+
for device_id, (_host_id, info, _active) in directory.items():
|
|
811
|
+
if str(device_id).isdigit():
|
|
812
|
+
types.setdefault(int(device_id), str(getattr(info, "kind", "") or "").strip().lower() or "unknown")
|
|
813
|
+
return {gid: re.sub(r"[^a-z0-9_]", "_", kind)[:64] for gid, kind in types.items()}
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def device_summary() -> list:
|
|
817
|
+
"""One entry per registered device: its type and whether it is connected.
|
|
818
|
+
|
|
819
|
+
Types and counts only — never a device identifier, serial, or any data.
|
|
820
|
+
"""
|
|
786
821
|
try:
|
|
787
822
|
from . import device_manager
|
|
788
823
|
|
|
789
824
|
connected = set(device_manager.get_all_devices())
|
|
790
825
|
except Exception: # noqa: BLE001 - drivers are optional at report time
|
|
791
826
|
connected = set()
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
827
|
+
return [{"type": kind, "online": gid in connected} for gid, kind in _device_types().items()]
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def usage_summary() -> dict:
|
|
831
|
+
"""Reservations made on the devices shared on Global: by the UTC hour and
|
|
832
|
+
weekday they start, and reservations and seconds reserved per device type.
|
|
833
|
+
|
|
834
|
+
Counts only (ReservationBuckets); all zero when nothing is shared there.
|
|
835
|
+
"""
|
|
836
|
+
from .federation import cluster
|
|
837
|
+
|
|
838
|
+
usage = {"by_hour": [0] * 24, "by_weekday": [0] * 7, "by_type": {}}
|
|
839
|
+
try:
|
|
840
|
+
shared = {int(d) for group in cluster.groups(cluster.GLOBAL) for d in group["devices"]}
|
|
841
|
+
except Exception: # noqa: BLE001 - not in the global cluster (yet), or its state is unreadable
|
|
842
|
+
return usage
|
|
843
|
+
types = _device_types()
|
|
844
|
+
by_type = {}
|
|
845
|
+
for device_id, bucket, n, seconds in _rows(
|
|
846
|
+
_db_dir() / "reservations.db",
|
|
847
|
+
"SELECT device_id, bucket, reservations, CAST(ROUND(seconds) AS INTEGER) FROM ReservationBuckets"):
|
|
848
|
+
if device_id not in shared:
|
|
795
849
|
continue
|
|
796
|
-
|
|
797
|
-
{
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
850
|
+
if bucket == "all":
|
|
851
|
+
totals = by_type.setdefault(types.get(device_id, "unknown"), {"reservations": 0, "seconds": 0})
|
|
852
|
+
totals["reservations"] += n
|
|
853
|
+
totals["seconds"] = min(totals["seconds"] + seconds, 2**31)
|
|
854
|
+
else:
|
|
855
|
+
usage["by_hour" if bucket[0] == "h" else "by_weekday"][int(bucket[1:])] += n
|
|
856
|
+
busiest = sorted((kv for kv in by_type.items() if kv[1]["reservations"]), key=lambda kv: -kv[1]["reservations"])
|
|
857
|
+
usage["by_type"] = dict(busiest[:64]) # Global takes at most 64 types
|
|
858
|
+
return usage
|
|
803
859
|
|
|
804
860
|
|
|
805
861
|
def cert_sha256() -> Optional[str]:
|
|
@@ -832,6 +888,7 @@ def status_payload() -> dict:
|
|
|
832
888
|
),
|
|
833
889
|
"reservations": _count(reservations, "SELECT n FROM ReservationCount"),
|
|
834
890
|
"reserved_s": _count(reservations, "SELECT CAST(ROUND(seconds) AS INTEGER) FROM ReservedTotal"),
|
|
891
|
+
"usage": usage_summary(),
|
|
835
892
|
"lan_address": lan_endpoint(),
|
|
836
893
|
"cert_sha256": cert_sha256(),
|
|
837
894
|
}
|
|
@@ -851,8 +908,9 @@ def send_status() -> None:
|
|
|
851
908
|
|
|
852
909
|
payload = status_payload()
|
|
853
910
|
# A Global older than a field refuses the note: drop the newest first
|
|
854
|
-
# (`reservations`), then `proxies`/`reserved_s`, as the note
|
|
855
|
-
|
|
911
|
+
# (`usage`, then `reservations`), then `proxies`/`reserved_s`, as the note
|
|
912
|
+
# was before each of them.
|
|
913
|
+
steps = ((), ("usage",), ("usage", "reservations"), ("usage", "reservations", "proxies", "reserved_s"))
|
|
856
914
|
for drop in steps:
|
|
857
915
|
try:
|
|
858
916
|
call("POST", REPORT_PATH, {"kind": "status", "status": {k: v for k, v in payload.items() if k not in drop}})
|
|
@@ -69,7 +69,6 @@ from prompt_toolkit import PromptSession
|
|
|
69
69
|
from prompt_toolkit.input import DummyInput
|
|
70
70
|
from prompt_toolkit.output import DummyOutput
|
|
71
71
|
|
|
72
|
-
from ..tools.gist_status import start_status_publisher
|
|
73
72
|
from .cert_provider import start_cert_provider, stop_cert_provider
|
|
74
73
|
try:
|
|
75
74
|
from . import direct_path
|
|
@@ -296,7 +295,6 @@ def start_server_runtime(
|
|
|
296
295
|
cert_dir: Path | None = None,
|
|
297
296
|
include_host_tunnel: bool = True,
|
|
298
297
|
run_reservation_updates: bool = True,
|
|
299
|
-
publish_status: bool = True,
|
|
300
298
|
federation_executor: Any = None,
|
|
301
299
|
home_federation_enroller: Any = None,
|
|
302
300
|
home_federation_permissions: Any = None,
|
|
@@ -444,8 +442,6 @@ def start_server_runtime(
|
|
|
444
442
|
)
|
|
445
443
|
update_thread.start()
|
|
446
444
|
start_usergroup_cleanup_loop(interval_sec=60)
|
|
447
|
-
if publish_status:
|
|
448
|
-
start_status_publisher()
|
|
449
445
|
except BaseException: # Ctrl-C during the startup check too: close_local_devices powers Plutos down
|
|
450
446
|
stop_event.set()
|
|
451
447
|
server.stop(0)
|
|
@@ -252,6 +252,46 @@ class ReservationHandler:
|
|
|
252
252
|
END;
|
|
253
253
|
''')
|
|
254
254
|
|
|
255
|
+
# The same two counts per own device: overall ('all'), by the UTC hour
|
|
256
|
+
# of the start ('h00'..'h23') and by its UTC weekday ('d0' = Monday ..
|
|
257
|
+
# 'd6'). Reservations hold local time, hence the 'utc' modifier.
|
|
258
|
+
# Reported to Global as `usage`, for the devices shared there. Seeded
|
|
259
|
+
# only into an empty table: the bucket names follow the process TZ, so a
|
|
260
|
+
# restart under another TZ must not count the current rows again.
|
|
261
|
+
hour = lambda r: f"'h' || strftime('%H', {r}.start_time, 'utc')" # noqa: E731
|
|
262
|
+
day = lambda r: f"'d' || ((CAST(strftime('%w', {r}.start_time, 'utc') AS INTEGER) + 6) % 7)" # noqa: E731
|
|
263
|
+
length = lambda r: f"(julianday({r}.end_time) - julianday({r}.start_time)) * 86400" # noqa: E731
|
|
264
|
+
self.cursor.executescript(f'''
|
|
265
|
+
BEGIN IMMEDIATE;
|
|
266
|
+
CREATE TABLE IF NOT EXISTS ReservationBuckets (device_id INTEGER NOT NULL, bucket TEXT NOT NULL,
|
|
267
|
+
reservations INTEGER NOT NULL, seconds REAL NOT NULL, PRIMARY KEY (device_id, bucket));
|
|
268
|
+
INSERT INTO ReservationBuckets
|
|
269
|
+
SELECT device_id, bucket, COUNT(*), SUM(seconds) FROM (
|
|
270
|
+
SELECT device_id, 'all' AS bucket, {length("r")} AS seconds FROM Reservations r
|
|
271
|
+
UNION ALL SELECT device_id, {hour("r")}, {length("r")} FROM Reservations r
|
|
272
|
+
UNION ALL SELECT device_id, {day("r")}, {length("r")} FROM Reservations r)
|
|
273
|
+
WHERE device_id < 1000000 AND NOT EXISTS (SELECT 1 FROM ReservationBuckets)
|
|
274
|
+
GROUP BY device_id, bucket;
|
|
275
|
+
CREATE TRIGGER IF NOT EXISTS reservation_buckets_ins AFTER INSERT ON Reservations
|
|
276
|
+
WHEN NEW.device_id < 1000000 BEGIN
|
|
277
|
+
INSERT INTO ReservationBuckets VALUES
|
|
278
|
+
(NEW.device_id, 'all', 1, {length("NEW")}),
|
|
279
|
+
(NEW.device_id, {hour("NEW")}, 1, {length("NEW")}),
|
|
280
|
+
(NEW.device_id, {day("NEW")}, 1, {length("NEW")})
|
|
281
|
+
ON CONFLICT (device_id, bucket) DO UPDATE
|
|
282
|
+
SET reservations = reservations + 1, seconds = seconds + excluded.seconds;
|
|
283
|
+
END;
|
|
284
|
+
CREATE TRIGGER IF NOT EXISTS reservation_buckets_del AFTER DELETE ON Reservations
|
|
285
|
+
WHEN OLD.device_id < 1000000 BEGIN
|
|
286
|
+
UPDATE ReservationBuckets SET
|
|
287
|
+
reservations = MAX(reservations - (OLD.start_time > datetime('now', 'localtime')), 0),
|
|
288
|
+
seconds = MAX(seconds - MAX(0,
|
|
289
|
+
(julianday(OLD.end_time) - julianday(MAX(OLD.start_time, datetime('now', 'localtime')))) * 86400), 0)
|
|
290
|
+
WHERE device_id = OLD.device_id AND bucket IN ('all', {hour("OLD")}, {day("OLD")});
|
|
291
|
+
END;
|
|
292
|
+
COMMIT;
|
|
293
|
+
''')
|
|
294
|
+
|
|
255
295
|
# "Stay logged in" for the deployment protocol: a random token the
|
|
256
296
|
# client keeps instead of the password. Only its hash lives here, so
|
|
257
297
|
# the table reveals nothing; a row is revoked by logout or purge.
|
|
@@ -1454,172 +1494,6 @@ class ReservationHandler:
|
|
|
1454
1494
|
except (sqlite3.Error, ValueError):
|
|
1455
1495
|
return {}
|
|
1456
1496
|
|
|
1457
|
-
@db_connection
|
|
1458
|
-
def get_obfuscated_device_reservations_grouped_past_days(self, days: int) -> dict[str, dict[str, int]]:
|
|
1459
|
-
try:
|
|
1460
|
-
days = int(days)
|
|
1461
|
-
except Exception:
|
|
1462
|
-
return {}
|
|
1463
|
-
|
|
1464
|
-
if days < 0:
|
|
1465
|
-
return {}
|
|
1466
|
-
|
|
1467
|
-
now = datetime.now()
|
|
1468
|
-
cutoff = now - timedelta(days=days)
|
|
1469
|
-
|
|
1470
|
-
def _parse_logged_int(v) -> int:
|
|
1471
|
-
if isinstance(v, int):
|
|
1472
|
-
return v
|
|
1473
|
-
if isinstance(v, float):
|
|
1474
|
-
return int(v)
|
|
1475
|
-
|
|
1476
|
-
s = str(v).strip()
|
|
1477
|
-
|
|
1478
|
-
# Handles strings like:
|
|
1479
|
-
# "int64_value: 2"
|
|
1480
|
-
# "int64_value: 1772587800"
|
|
1481
|
-
if ":" in s:
|
|
1482
|
-
s = s.split(":", 1)[1].strip()
|
|
1483
|
-
|
|
1484
|
-
# remove quotes/newlines if present
|
|
1485
|
-
s = s.strip().strip('"').strip("'")
|
|
1486
|
-
return int(float(s))
|
|
1487
|
-
|
|
1488
|
-
# Take a consistent snapshot of the log file under the same lock used by writers.
|
|
1489
|
-
try:
|
|
1490
|
-
with log_lock:
|
|
1491
|
-
activity_log = _load_logs()
|
|
1492
|
-
except Exception:
|
|
1493
|
-
activity_log = []
|
|
1494
|
-
|
|
1495
|
-
out: dict[str, dict[str, int]] = {}
|
|
1496
|
-
|
|
1497
|
-
for entry in activity_log:
|
|
1498
|
-
try:
|
|
1499
|
-
if not isinstance(entry, dict):
|
|
1500
|
-
continue
|
|
1501
|
-
|
|
1502
|
-
if str(entry.get("action", "")).strip() != "reserve_device":
|
|
1503
|
-
continue
|
|
1504
|
-
|
|
1505
|
-
details = entry.get("details", {})
|
|
1506
|
-
if not isinstance(details, dict):
|
|
1507
|
-
continue
|
|
1508
|
-
|
|
1509
|
-
args = details.get("args", {})
|
|
1510
|
-
result = details.get("result", {})
|
|
1511
|
-
|
|
1512
|
-
if not isinstance(args, dict) or not isinstance(result, dict):
|
|
1513
|
-
continue
|
|
1514
|
-
|
|
1515
|
-
# Only count SUCCESSFUL reservations
|
|
1516
|
-
if "Token" not in result:
|
|
1517
|
-
continue
|
|
1518
|
-
|
|
1519
|
-
raw_device_id = args.get("dd")
|
|
1520
|
-
raw_start = args.get("st")
|
|
1521
|
-
raw_end = args.get("et")
|
|
1522
|
-
|
|
1523
|
-
if raw_device_id is None or raw_start is None or raw_end is None:
|
|
1524
|
-
continue
|
|
1525
|
-
|
|
1526
|
-
device_id = _parse_logged_int(raw_device_id)
|
|
1527
|
-
start_ts = _parse_logged_int(raw_start)
|
|
1528
|
-
end_ts = _parse_logged_int(raw_end)
|
|
1529
|
-
|
|
1530
|
-
start_dt = datetime.fromtimestamp(start_ts)
|
|
1531
|
-
end_dt = datetime.fromtimestamp(end_ts)
|
|
1532
|
-
|
|
1533
|
-
# Same overlap semantics as before
|
|
1534
|
-
if end_dt < cutoff or start_dt > now:
|
|
1535
|
-
continue
|
|
1536
|
-
|
|
1537
|
-
dev_key = str(device_id)
|
|
1538
|
-
day_key = start_dt.date().isoformat()
|
|
1539
|
-
|
|
1540
|
-
if dev_key not in out:
|
|
1541
|
-
out[dev_key] = {}
|
|
1542
|
-
|
|
1543
|
-
out[dev_key][day_key] = out[dev_key].get(day_key, 0) + 1
|
|
1544
|
-
|
|
1545
|
-
except Exception:
|
|
1546
|
-
continue
|
|
1547
|
-
|
|
1548
|
-
return dict(sorted(out.items(), key=lambda kv: int(kv[0])))
|
|
1549
|
-
|
|
1550
|
-
@db_connection
|
|
1551
|
-
def get_obfuscated_usage_summary(self) -> dict[str, int | float]:
|
|
1552
|
-
|
|
1553
|
-
int_re = re.compile(r'int64_value:\s*([0-9]+)')
|
|
1554
|
-
str_re = re.compile(r'string_value:\s*"([^"]*)"')
|
|
1555
|
-
|
|
1556
|
-
def _parse_proto_value(v):
|
|
1557
|
-
if isinstance(v, (int, float)):
|
|
1558
|
-
return v
|
|
1559
|
-
if not isinstance(v, str):
|
|
1560
|
-
return v
|
|
1561
|
-
|
|
1562
|
-
m_int = int_re.search(v)
|
|
1563
|
-
if m_int:
|
|
1564
|
-
return int(m_int.group(1))
|
|
1565
|
-
|
|
1566
|
-
m_str = str_re.search(v)
|
|
1567
|
-
if m_str:
|
|
1568
|
-
return m_str.group(1)
|
|
1569
|
-
|
|
1570
|
-
return v
|
|
1571
|
-
|
|
1572
|
-
try:
|
|
1573
|
-
with log_lock:
|
|
1574
|
-
activity_log = _load_logs()
|
|
1575
|
-
except Exception:
|
|
1576
|
-
activity_log = []
|
|
1577
|
-
|
|
1578
|
-
accounts: set[str] = set()
|
|
1579
|
-
total_reservations = 0
|
|
1580
|
-
total_reserved_seconds = 0
|
|
1581
|
-
|
|
1582
|
-
for entry in activity_log:
|
|
1583
|
-
try:
|
|
1584
|
-
if not isinstance(entry, dict):
|
|
1585
|
-
continue
|
|
1586
|
-
|
|
1587
|
-
action = entry.get("action")
|
|
1588
|
-
username = entry.get("username")
|
|
1589
|
-
details = entry.get("details", {}) or {}
|
|
1590
|
-
args = details.get("args", {}) or {}
|
|
1591
|
-
result = details.get("result", {}) or {}
|
|
1592
|
-
|
|
1593
|
-
if not isinstance(args, dict) or not isinstance(result, dict):
|
|
1594
|
-
continue
|
|
1595
|
-
|
|
1596
|
-
# Count accounts from successful logins
|
|
1597
|
-
if action == "login" and "UC" in result:
|
|
1598
|
-
uc_name = _parse_proto_value(result.get("UC"))
|
|
1599
|
-
if uc_name:
|
|
1600
|
-
accounts.add(str(uc_name))
|
|
1601
|
-
elif username:
|
|
1602
|
-
accounts.add(str(username))
|
|
1603
|
-
|
|
1604
|
-
# Count reservations + reserved time
|
|
1605
|
-
if action == "reserve_device":
|
|
1606
|
-
total_reservations += 1
|
|
1607
|
-
|
|
1608
|
-
st = _parse_proto_value(args.get("st"))
|
|
1609
|
-
et = _parse_proto_value(args.get("et"))
|
|
1610
|
-
|
|
1611
|
-
if isinstance(st, int) and isinstance(et, int) and et > st:
|
|
1612
|
-
total_reserved_seconds += (et - st)
|
|
1613
|
-
|
|
1614
|
-
except Exception:
|
|
1615
|
-
continue
|
|
1616
|
-
|
|
1617
|
-
return {
|
|
1618
|
-
"account_count": len(accounts),
|
|
1619
|
-
"total_reservations": int(total_reservations),
|
|
1620
|
-
"total_reserved_hours": round(total_reserved_seconds / 3600.0, 1),
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
1497
|
def get_reservation_count(self, username):
|
|
1624
1498
|
self.cursor.execute("SELECT COUNT(*) FROM Reservations WHERE username = ?", (username,))
|
|
1625
1499
|
count = self.cursor.fetchone()[0]
|