Update operator profile creation to use vless/hysteria2 code with location from SQLite
This commit is contained in:
parent
c458d4da95
commit
4b0e6c1a3f
5 changed files with 177 additions and 191 deletions
13
README.md
13
README.md
|
|
@ -102,6 +102,19 @@ sudo docker compose exec laravel php artisan tinker --execute="
|
||||||
\$sub->save();
|
\$sub->save();
|
||||||
echo 'OK' . PHP_EOL;
|
echo 'OK' . PHP_EOL;
|
||||||
"
|
"
|
||||||
|
|
||||||
|
|
||||||
|
docker compose exec laravel php artisan tinker --execute="
|
||||||
|
\$sub = new \App\Models\Subscription();
|
||||||
|
\$sub->subscription_plan_id = 3;
|
||||||
|
\$sub->operator_id = 3;
|
||||||
|
\$sub->billing_code = 'TEST-' . strtoupper(substr(uniqid(), -4)) . '-' . strtoupper(substr(uniqid(), -4)) . '-' . strtoupper(substr(uniqid(), -4));
|
||||||
|
\$sub->payment_reference = 'bypass_' . uniqid();
|
||||||
|
\$sub->duration = 720;
|
||||||
|
\$sub->expires_at = '2027-12-31 23:59:59';
|
||||||
|
\$sub->save();
|
||||||
|
echo \$sub->billing_code . PHP_EOL;
|
||||||
|
"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then enable again:
|
Then enable again:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,18 @@ if __name__ == '__main__':
|
||||||
]:
|
]:
|
||||||
Path(path).mkdir(parents=True, exist_ok=True, mode=0o700)
|
Path(path).mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||||
|
|
||||||
|
# Bootstrap DB if needed
|
||||||
|
try:
|
||||||
|
from core.models.manage.session_management import create_ALL_tables, create_ONLY_db_version_table
|
||||||
|
from core.models.manage.version_check import insert_new_version, get_database_version
|
||||||
|
from core.models.manage.session_management import get_session
|
||||||
|
create_ONLY_db_version_table()
|
||||||
|
if get_database_version(get_session()) is None:
|
||||||
|
create_ALL_tables()
|
||||||
|
insert_new_version(get_session(), Constants.DB_VERSION_THIS_APP_WANTS)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
sys.excepthook = _handle_exception
|
sys.excepthook = _handle_exception
|
||||||
|
|
||||||
distribution = get_distribution()
|
distribution = get_distribution()
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,21 @@ def handle(arguments, main_parser):
|
||||||
elif arguments.subcommand == 'create':
|
elif arguments.subcommand == 'create':
|
||||||
|
|
||||||
if arguments.profile_type == 'operator':
|
if arguments.profile_type == 'operator':
|
||||||
connection = SystemConnection('operator', arguments.operator_id, arguments.protocol)
|
from core.models.manage.session_management import get_session
|
||||||
profile = SystemProfile(arguments.id, arguments.name, None, None, connection)
|
from core.models.orm_models.Location import Location
|
||||||
|
from sqlalchemy import select
|
||||||
|
session = get_session()
|
||||||
|
capable_field = Location.is_vless_capable if arguments.protocol == 'vless' else Location.is_hysteria2_capable
|
||||||
|
loc = session.execute(
|
||||||
|
select(Location).where(
|
||||||
|
(Location.operator_id == arguments.operator_id) &
|
||||||
|
(capable_field == True)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if loc is None:
|
||||||
|
main_parser.error(f'No location found for operator {arguments.operator_id} with protocol {arguments.protocol}')
|
||||||
|
connection = SystemConnection(arguments.protocol, arguments.operator_id)
|
||||||
|
profile = SystemProfile(arguments.id, arguments.name, None, loc, connection)
|
||||||
ProfileController.create(profile, profile_observer=profile_observer)
|
ProfileController.create(profile, profile_observer=profile_observer)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -119,8 +132,7 @@ def handle(arguments, main_parser):
|
||||||
|
|
||||||
monitor = KillswitchMonitor()
|
monitor = KillswitchMonitor()
|
||||||
timer_state = {'stop': None, 'reconnecting': None, '_retrying': False}
|
timer_state = {'stop': None, 'reconnecting': None, '_retrying': False}
|
||||||
obs._timer_state = timer_state
|
obs.get_ui_state()['timer_state'] = timer_state
|
||||||
|
|
||||||
def _stop_timer():
|
def _stop_timer():
|
||||||
if timer_state['stop'] is not None:
|
if timer_state['stop'] is not None:
|
||||||
timer_state['stop'].set()
|
timer_state['stop'].set()
|
||||||
|
|
@ -155,7 +167,7 @@ def handle(arguments, main_parser):
|
||||||
_do_disable()
|
_do_disable()
|
||||||
elif choice == '2':
|
elif choice == '2':
|
||||||
try:
|
try:
|
||||||
obs._timer_state['_retrying'] = True
|
obs.get_ui_state()['timer_state']['_retrying'] = True
|
||||||
ProfileController.enable(
|
ProfileController.enable(
|
||||||
profile, ignore=ignore, pristine=False,
|
profile, ignore=ignore, pristine=False,
|
||||||
asynchronous=True, profile_observer=profile_observer,
|
asynchronous=True, profile_observer=profile_observer,
|
||||||
|
|
@ -189,7 +201,7 @@ def handle(arguments, main_parser):
|
||||||
monitor.set_on_disconnect(_do_disable)
|
monitor.set_on_disconnect(_do_disable)
|
||||||
monitor.set_on_leak(_on_leak)
|
monitor.set_on_leak(_on_leak)
|
||||||
monitor.set_on_displaced(_on_displaced)
|
monitor.set_on_displaced(_on_displaced)
|
||||||
obs._monitor = monitor
|
obs.get_ui_state()['monitor'] = monitor
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ProfileController.enable(
|
ProfileController.enable(
|
||||||
|
|
|
||||||
|
|
@ -2,32 +2,29 @@ import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from core.utils.encrypted_proxy import killswitch
|
from core.utils.encrypted_proxy import killswitch
|
||||||
from core.utils.encrypted_proxy.singbox import SingboxRunner
|
from core.utils.encrypted_proxy.singbox import SingboxRunner, SingboxProcessMonitor
|
||||||
|
from core.utils.encrypted_proxy.singbox import CAUSE_DISCONNECT, CAUSE_LEAK, CAUSE_DISPLACED
|
||||||
|
|
||||||
drop_state: dict = {"count": 0}
|
drop_state: dict = {"count": 0}
|
||||||
tunnel_state: dict = {"alive": True}
|
tunnel_state: dict = {"alive": True}
|
||||||
|
|
||||||
CAUSE_DISCONNECT = 'disconnect'
|
|
||||||
CAUSE_LEAK = 'leak'
|
|
||||||
CAUSE_DISPLACED = 'displaced'
|
|
||||||
|
|
||||||
|
class KillswitchMonitor(SingboxProcessMonitor):
|
||||||
|
"""CLI-specific monitor: adds killswitch + drops detection."""
|
||||||
|
|
||||||
class KillswitchMonitor:
|
LOG_PREFIX = "hydraveil-drop"
|
||||||
|
|
||||||
POLL_INTERVAL = 5
|
|
||||||
LOG_PREFIX = "hydraveil-drop"
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._on_disconnect = None
|
super().__init__(SingboxRunner())
|
||||||
self._on_leak = None
|
self._on_disconnect = None
|
||||||
self._on_displaced = None
|
self._on_leak = None
|
||||||
self._socks5_port = None
|
self._on_displaced = None
|
||||||
self._session_token = None
|
self._socks5_port = None
|
||||||
self._running = False
|
self._session_token = None
|
||||||
self._stop_event = threading.Event()
|
self._cause_event = threading.Event()
|
||||||
self._cause_event = threading.Event()
|
|
||||||
self._cause: str | None = None
|
self._cause: str | None = None
|
||||||
self._last_poll_ts: str | None = None
|
|
||||||
|
# ── Setters ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def set_on_disconnect(self, fn):
|
def set_on_disconnect(self, fn):
|
||||||
self._on_disconnect = fn
|
self._on_disconnect = fn
|
||||||
|
|
@ -44,6 +41,65 @@ class KillswitchMonitor:
|
||||||
def set_session_token(self, token: str):
|
def set_session_token(self, token: str):
|
||||||
self._session_token = token
|
self._session_token = token
|
||||||
|
|
||||||
|
# ── Abstract impl ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def on_process_alive(self) -> None:
|
||||||
|
tunnel_state["alive"] = True
|
||||||
|
|
||||||
|
def on_process_dead(self) -> None:
|
||||||
|
tunnel_state["alive"] = False
|
||||||
|
|
||||||
|
def on_monitor_stopped(self, cause: str) -> None:
|
||||||
|
if cause == CAUSE_DISCONNECT and self._on_disconnect:
|
||||||
|
self._on_disconnect()
|
||||||
|
elif cause == CAUSE_LEAK and self._on_leak:
|
||||||
|
self._on_leak()
|
||||||
|
elif cause == CAUSE_DISPLACED and self._on_displaced:
|
||||||
|
self._on_displaced()
|
||||||
|
|
||||||
|
# ── Override monitor loop to add CLI-specific checks ──────────────────────
|
||||||
|
|
||||||
|
def _monitor_loop(self) -> None:
|
||||||
|
while True:
|
||||||
|
with self._lock:
|
||||||
|
if not self._running:
|
||||||
|
break
|
||||||
|
|
||||||
|
interrupted = self._stop_event.wait(timeout=self.POLL_INTERVAL)
|
||||||
|
if interrupted:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Killswitch check
|
||||||
|
if not killswitch.status():
|
||||||
|
tunnel_state["alive"] = False
|
||||||
|
self._trigger(CAUSE_DISCONNECT)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Sing-box check
|
||||||
|
alive = self._runner.is_running()
|
||||||
|
tunnel_state["alive"] = alive
|
||||||
|
if not alive:
|
||||||
|
self._trigger(CAUSE_LEAK)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Session token check
|
||||||
|
if self._session_token is not None:
|
||||||
|
from core.models.system.SystemState import SystemState
|
||||||
|
current = SystemState.get()
|
||||||
|
if current is None or current.session_token != self._session_token:
|
||||||
|
tunnel_state["alive"] = False
|
||||||
|
self._trigger(CAUSE_DISPLACED)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._poll_drops()
|
||||||
|
|
||||||
|
def _trigger(self, cause: str):
|
||||||
|
self._cause = cause
|
||||||
|
with self._lock:
|
||||||
|
self._running = False
|
||||||
|
self._cause_event.set()
|
||||||
|
self._stop_event.set()
|
||||||
|
|
||||||
def _poll_drops(self) -> None:
|
def _poll_drops(self) -> None:
|
||||||
since = self._last_poll_ts or "10 seconds ago"
|
since = self._last_poll_ts or "10 seconds ago"
|
||||||
try:
|
try:
|
||||||
|
|
@ -66,65 +122,22 @@ class KillswitchMonitor:
|
||||||
finally:
|
finally:
|
||||||
self._last_poll_ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
self._last_poll_ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
def stop(self):
|
# ── Override run_blocking to handle cause_event ───────────────────────────
|
||||||
self._running = False
|
|
||||||
self._stop_event.set()
|
|
||||||
|
|
||||||
def _trigger(self, cause: str):
|
def run_blocking(self) -> None:
|
||||||
self._cause = cause
|
with self._lock:
|
||||||
self._running = False
|
self._running = True
|
||||||
self._cause_event.set()
|
|
||||||
self._stop_event.set()
|
|
||||||
|
|
||||||
def _monitor_loop(self):
|
|
||||||
while self._running:
|
|
||||||
interrupted = self._stop_event.wait(timeout=self.POLL_INTERVAL)
|
|
||||||
if interrupted or not self._running:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not killswitch.status():
|
|
||||||
tunnel_state["alive"] = False
|
|
||||||
self._trigger(CAUSE_DISCONNECT)
|
|
||||||
return
|
|
||||||
|
|
||||||
singbox_alive = SingboxRunner().is_running()
|
|
||||||
tunnel_state["alive"] = singbox_alive
|
|
||||||
|
|
||||||
if not singbox_alive:
|
|
||||||
self._trigger(CAUSE_LEAK)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._session_token is not None:
|
|
||||||
from core.models.system.SystemState import SystemState
|
|
||||||
current = SystemState.get()
|
|
||||||
if current is None or current.session_token != self._session_token:
|
|
||||||
tunnel_state["alive"] = False
|
|
||||||
self._trigger(CAUSE_DISPLACED)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._poll_drops()
|
|
||||||
|
|
||||||
def run_blocking(self):
|
|
||||||
self._running = True
|
|
||||||
self._stop_event.clear()
|
self._stop_event.clear()
|
||||||
self._cause_event.clear()
|
self._cause_event.clear()
|
||||||
self._cause = None
|
self._cause = None
|
||||||
drop_state["count"] = 0
|
drop_state["count"] = 0
|
||||||
tunnel_state["alive"] = True
|
tunnel_state["alive"] = True
|
||||||
self._last_poll_ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
self._last_poll_ts = time.strftime("%Y-%m-%dT%H:%M:%S")
|
||||||
|
|
||||||
t = threading.Thread(target=self._monitor_loop, daemon=True)
|
t = threading.Thread(target=self._monitor_loop, daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
self._cause_event.wait()
|
self._cause_event.wait()
|
||||||
t.join(timeout=2)
|
t.join(timeout=2)
|
||||||
|
|
||||||
cause = self._cause
|
self.on_monitor_stopped(self._cause or "user_requested")
|
||||||
if cause == CAUSE_DISCONNECT:
|
|
||||||
if self._on_disconnect:
|
|
||||||
self._on_disconnect()
|
|
||||||
elif cause == CAUSE_LEAK:
|
|
||||||
if self._on_leak:
|
|
||||||
self._on_leak()
|
|
||||||
elif cause == CAUSE_DISPLACED:
|
|
||||||
if self._on_displaced:
|
|
||||||
self._on_displaced()
|
|
||||||
168
cli/observers.py
168
cli/observers.py
|
|
@ -1,158 +1,94 @@
|
||||||
from core.utils.encrypted_proxy import killswitch
|
|
||||||
from core.controllers.ApplicationController import ApplicationController
|
|
||||||
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
|
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
|
||||||
from core.observers.ClientObserver import ClientObserver
|
from core.observers.ClientObserver import ClientObserver
|
||||||
from core.observers.ConnectionObserver import ConnectionObserver
|
from core.observers.ConnectionObserver import ConnectionObserver
|
||||||
from core.observers.InvoiceObserver import InvoiceObserver
|
from core.observers.InvoiceObserver import InvoiceObserver
|
||||||
from core.observers.ProfileObserver import ProfileObserver
|
from core.observers.ProfileObserver import ProfileObserver
|
||||||
from cli.ui import (
|
from core.services.encrypted_proxy.observer_helpers import create_ui_state, create_event_handlers
|
||||||
Spinner, label_ok, label_error, label_warn,
|
from core.controllers.ApplicationController import ApplicationController
|
||||||
label_connected, label_disconnect, label_killswitch,
|
|
||||||
label_dns, label_ipv6, label_ipv4, progress_bar,
|
|
||||||
)
|
|
||||||
from cli.helpers import sanitize_profile
|
|
||||||
from cli.killswitch_monitor import drop_state, tunnel_state
|
from cli.killswitch_monitor import drop_state, tunnel_state
|
||||||
|
from cli.ui import progress_bar
|
||||||
|
from cli.helpers import sanitize_profile
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
|
# ── Observer instances ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
application_version_observer = ApplicationVersionObserver()
|
application_version_observer = ApplicationVersionObserver()
|
||||||
client_observer = ClientObserver()
|
client_observer = ClientObserver()
|
||||||
connection_observer = ConnectionObserver()
|
connection_observer = ConnectionObserver()
|
||||||
invoice_observer = InvoiceObserver()
|
invoice_observer = InvoiceObserver()
|
||||||
profile_observer = ProfileObserver()
|
profile_observer = ProfileObserver()
|
||||||
|
|
||||||
_spinner = None
|
# ── Shared UI state (used by observer_helpers) ────────────────────────────────
|
||||||
_monitor = None
|
|
||||||
_timer_state = None
|
|
||||||
|
|
||||||
|
_ui_state = create_ui_state()
|
||||||
|
|
||||||
application_version_observer.subscribe('downloading', lambda event: print(f'Downloading {ApplicationController.get(event.subject.application_code).name}, version {event.subject.version_number}...'))
|
# Public refs for profile.py to access monitor and timer
|
||||||
application_version_observer.subscribe('download_progressing',lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
|
def get_ui_state():
|
||||||
application_version_observer.subscribe('downloaded', lambda event: print('\n'))
|
return _ui_state
|
||||||
|
|
||||||
client_observer.subscribe('synchronizing', lambda event: print('Synchronizing...\n'))
|
# ── Connection event handlers (from core) ─────────────────────────────────────
|
||||||
client_observer.subscribe('updating', lambda event: print('Updating client...'))
|
|
||||||
client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
|
|
||||||
client_observer.subscribe('updated', lambda event: print('\n'))
|
|
||||||
|
|
||||||
|
_handlers = create_event_handlers(_ui_state, drop_state, tunnel_state)
|
||||||
|
|
||||||
def _on_connecting(event):
|
connection_observer.subscribe('connecting', _handlers['on_connecting'])
|
||||||
global _spinner, _timer_state
|
connection_observer.subscribe('connected', _handlers['on_connected'])
|
||||||
if _timer_state and _timer_state['stop']:
|
connection_observer.subscribe('connected_token', _handlers['on_connected_token'])
|
||||||
_timer_state['stop'].set()
|
connection_observer.subscribe('disconnected', _handlers['on_disconnected'])
|
||||||
_timer_state['stop'] = None
|
connection_observer.subscribe('error', _handlers['on_error'])
|
||||||
if _timer_state and _timer_state['reconnecting'] is None and _timer_state.get('_retrying'):
|
|
||||||
from cli.ui import connecting_spinner
|
|
||||||
_timer_state['reconnecting'] = connecting_spinner()
|
|
||||||
attempt = event.subject.get("attempt_count", "?")
|
|
||||||
total = event.subject.get("maximum_number_of_attempts", "?")
|
|
||||||
_spinner = Spinner(f"Connecting... attempt {attempt}/{total}")
|
|
||||||
_spinner.start()
|
|
||||||
|
|
||||||
|
|
||||||
def _on_connected(event):
|
|
||||||
global _spinner, _monitor, _timer_state
|
|
||||||
if _spinner:
|
|
||||||
_spinner.stop()
|
|
||||||
_spinner = None
|
|
||||||
if _timer_state and _timer_state.get('reconnecting'):
|
|
||||||
_timer_state['reconnecting'].set()
|
|
||||||
_timer_state['reconnecting'] = None
|
|
||||||
if _timer_state:
|
|
||||||
_timer_state['_retrying'] = False
|
|
||||||
|
|
||||||
d = event.subject or {}
|
|
||||||
tunnel_if = d.get('tunnel_if', '?')
|
|
||||||
server_ip = d.get('server_ip')
|
|
||||||
socks5_port = d.get('socks5_port', '?')
|
|
||||||
|
|
||||||
label_connected(f"tunnel={tunnel_if} port={socks5_port}")
|
|
||||||
label_killswitch(killswitch.status())
|
|
||||||
label_dns(
|
|
||||||
enabled=d.get('dns_enabled', True),
|
|
||||||
active=d.get('dns_active', False),
|
|
||||||
)
|
|
||||||
label_ipv6(blocked=True)
|
|
||||||
if server_ip:
|
|
||||||
label_ipv4(server_ip=server_ip, reachable=True)
|
|
||||||
|
|
||||||
if _monitor is not None:
|
|
||||||
_monitor.set_connection_data(socks5_port=d.get('socks5_port'))
|
|
||||||
|
|
||||||
if _timer_state is not None:
|
|
||||||
from cli.ui import session_timer
|
|
||||||
_timer_state['stop'] = session_timer(drop_state=drop_state, tunnel_state=tunnel_state)
|
|
||||||
|
|
||||||
|
|
||||||
def _on_connected_token(event):
|
|
||||||
global _monitor
|
|
||||||
if _monitor is not None:
|
|
||||||
token = (event.subject or {}).get('session_token')
|
|
||||||
if token:
|
|
||||||
_monitor.set_session_token(token)
|
|
||||||
|
|
||||||
|
|
||||||
def _on_disconnected(event):
|
|
||||||
global _spinner, _monitor, _timer_state
|
|
||||||
if _spinner:
|
|
||||||
_spinner.stop()
|
|
||||||
_spinner = None
|
|
||||||
if _timer_state and _timer_state['stop']:
|
|
||||||
_timer_state['stop'].set()
|
|
||||||
_timer_state['stop'] = None
|
|
||||||
if _monitor is not None:
|
|
||||||
_monitor.stop()
|
|
||||||
_monitor = None
|
|
||||||
d = event.subject or {}
|
|
||||||
label_disconnect(f"tunnel={d.get('tunnel_if','?')}")
|
|
||||||
|
|
||||||
|
|
||||||
def _on_error(event):
|
|
||||||
global _spinner, _monitor, _timer_state
|
|
||||||
if _spinner:
|
|
||||||
_spinner.stop()
|
|
||||||
_spinner = None
|
|
||||||
if _timer_state and _timer_state['stop']:
|
|
||||||
_timer_state['stop'].set()
|
|
||||||
_timer_state['stop'] = None
|
|
||||||
if _monitor is not None:
|
|
||||||
_monitor.stop()
|
|
||||||
_monitor = None
|
|
||||||
label_error(str(event.subject or "Unknown error"))
|
|
||||||
|
|
||||||
|
# ── Tor events ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def _on_tor_bootstrapping(event):
|
def _on_tor_bootstrapping(event):
|
||||||
global _spinner
|
from cli.ui import Spinner
|
||||||
_spinner = Spinner("Starting Tor...")
|
_ui_state['spinner'] = Spinner("Starting Tor...")
|
||||||
_spinner.start()
|
_ui_state['spinner'].start()
|
||||||
|
|
||||||
|
|
||||||
def _on_tor_bootstrap_progressing(event):
|
def _on_tor_bootstrap_progressing(event):
|
||||||
pct = int(event.meta.get("progress", 0))
|
pct = int(event.meta.get("progress", 0))
|
||||||
progress_bar(pct, 100, "Tor bootstrap")
|
progress_bar(pct, 100, "Tor bootstrap")
|
||||||
|
|
||||||
|
|
||||||
def _on_tor_bootstrapped(event):
|
def _on_tor_bootstrapped(event):
|
||||||
global _spinner
|
from cli.ui import label_ok
|
||||||
if _spinner:
|
if _ui_state['spinner']:
|
||||||
_spinner.stop()
|
_ui_state['spinner'].stop()
|
||||||
_spinner = None
|
_ui_state['spinner'] = None
|
||||||
print()
|
print()
|
||||||
label_ok("Tor ready")
|
label_ok("Tor ready")
|
||||||
|
|
||||||
|
|
||||||
connection_observer.subscribe('connecting', _on_connecting)
|
|
||||||
connection_observer.subscribe('connected', _on_connected)
|
|
||||||
connection_observer.subscribe('connected_token', _on_connected_token)
|
|
||||||
connection_observer.subscribe('disconnected', _on_disconnected)
|
|
||||||
connection_observer.subscribe('error', _on_error)
|
|
||||||
connection_observer.subscribe('tor_bootstrapping', _on_tor_bootstrapping)
|
connection_observer.subscribe('tor_bootstrapping', _on_tor_bootstrapping)
|
||||||
connection_observer.subscribe('tor_bootstrap_progressing', _on_tor_bootstrap_progressing)
|
connection_observer.subscribe('tor_bootstrap_progressing', _on_tor_bootstrap_progressing)
|
||||||
connection_observer.subscribe('tor_bootstrapped', _on_tor_bootstrapped)
|
connection_observer.subscribe('tor_bootstrapped', _on_tor_bootstrapped)
|
||||||
|
|
||||||
|
# ── Application version events ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
application_version_observer.subscribe(
|
||||||
|
'downloading',
|
||||||
|
lambda event: print(
|
||||||
|
f'Downloading {ApplicationController.get(event.subject.application_code).name}, '
|
||||||
|
f'version {event.subject.version_number}...'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
application_version_observer.subscribe(
|
||||||
|
'download_progressing',
|
||||||
|
lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r')
|
||||||
|
)
|
||||||
|
application_version_observer.subscribe('downloaded', lambda event: print('\n'))
|
||||||
|
|
||||||
|
# ── Client events ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
client_observer.subscribe('synchronizing', lambda event: print('Synchronizing...\n'))
|
||||||
|
client_observer.subscribe('updating', lambda event: print('Updating client...'))
|
||||||
|
client_observer.subscribe('update_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
|
||||||
|
client_observer.subscribe('updated', lambda event: print('\n'))
|
||||||
|
|
||||||
|
# ── Invoice events ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
invoice_observer.subscribe('retrieved', lambda event: print(f'\n{pprint.pp(event.subject)}\n'))
|
invoice_observer.subscribe('retrieved', lambda event: print(f'\n{pprint.pp(event.subject)}\n'))
|
||||||
invoice_observer.subscribe('processing', lambda event: print('A payment has been detected and is being verified...\n'))
|
invoice_observer.subscribe('processing', lambda event: print('A payment has been detected and is being verified...\n'))
|
||||||
invoice_observer.subscribe('settled', lambda event: print('The payment has been successfully verified.\n'))
|
invoice_observer.subscribe('settled', lambda event: print('The payment has been successfully verified.\n'))
|
||||||
|
|
||||||
|
# ── Profile events ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
profile_observer.subscribe('created', lambda event: pprint.pp((sanitize_profile(event.subject), 'Created')))
|
profile_observer.subscribe('created', lambda event: pprint.pp((sanitize_profile(event.subject), 'Created')))
|
||||||
profile_observer.subscribe('destroyed', lambda event: pprint.pp((sanitize_profile(event.subject), 'Destroyed')))
|
profile_observer.subscribe('destroyed', lambda event: pprint.pp((sanitize_profile(event.subject), 'Destroyed')))
|
||||||
profile_observer.subscribe('disabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Disabled')) if event.meta.get('explicitly') else None)
|
profile_observer.subscribe('disabled', lambda event: pprint.pp((sanitize_profile(event.subject), 'Disabled')) if event.meta.get('explicitly') else None)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue