radical change
This commit is contained in:
parent
cd4ee8aaaa
commit
445bd65b4a
3 changed files with 204 additions and 7 deletions
|
|
@ -117,11 +117,10 @@ def handle(arguments, main_parser):
|
||||||
main_parser.error('sing-box is not installed. Please run the installation script first.')
|
main_parser.error('sing-box is not installed. Please run the installation script first.')
|
||||||
except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
|
except (InvalidSubscriptionError, MissingSubscriptionError) as exception:
|
||||||
_handle_subscription_error(exception, profile, ignore, arguments, main_parser)
|
_handle_subscription_error(exception, profile, ignore, arguments, main_parser)
|
||||||
|
|
||||||
elif arguments.subcommand == 'disable':
|
elif arguments.subcommand == 'disable':
|
||||||
profile = ProfileController.get(arguments.id)
|
profile = ProfileController.get(arguments.id)
|
||||||
if profile is not None:
|
if profile is not None:
|
||||||
ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer)
|
ProfileController.disable(profile, ignore=ignore, profile_observer=profile_observer, connection_observer=connection_observer)
|
||||||
else:
|
else:
|
||||||
main_parser.error('the following argument should be a valid reference: --id/-i')
|
main_parser.error('the following argument should be a valid reference: --id/-i')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ 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 Spinner, label_ok, label_error, label_warn, label_connected, label_disconnect, progress_bar
|
||||||
from cli.helpers import sanitize_profile
|
from cli.helpers import sanitize_profile
|
||||||
import pprint
|
import pprint
|
||||||
|
|
||||||
|
|
@ -22,10 +23,63 @@ 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('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'))
|
client_observer.subscribe('updated', lambda event: print('\n'))
|
||||||
|
|
||||||
connection_observer.subscribe('connecting', lambda event: print(f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...\n'))
|
_spinner = None
|
||||||
connection_observer.subscribe('tor_bootstrapping', lambda event: print('Bootstrapping Tor...'))
|
|
||||||
connection_observer.subscribe('tor_bootstrap_progressing', lambda event: print(f'Current progress: {event.meta.get("progress"):.2f}%', flush=True, end='\r'))
|
def _on_connecting(event):
|
||||||
connection_observer.subscribe('tor_bootstrapped', lambda event: print('\n'))
|
global _spinner
|
||||||
|
attempt = event.subject.get("attempt_count", "?")
|
||||||
|
total = event.subject.get("maximum_number_of_attempts", "?")
|
||||||
|
_spinner = Spinner(f"Connecting... [{attempt}/{total}]")
|
||||||
|
_spinner.start()
|
||||||
|
|
||||||
|
def _on_connected(event):
|
||||||
|
global _spinner
|
||||||
|
if _spinner:
|
||||||
|
_spinner.stop()
|
||||||
|
_spinner = None
|
||||||
|
d = event.subject or {}
|
||||||
|
label_connected(f"proxy={d.get('proxy_ip','?')} real={d.get('real_ip','?')} port={d.get('socks5_port','?')}")
|
||||||
|
|
||||||
|
def _on_disconnected(event):
|
||||||
|
global _spinner
|
||||||
|
if _spinner:
|
||||||
|
_spinner.stop()
|
||||||
|
_spinner = None
|
||||||
|
d = event.subject or {}
|
||||||
|
ip = d.get("ip", "?")
|
||||||
|
label_disconnect(f"real={ip}")
|
||||||
|
|
||||||
|
def _on_error(event):
|
||||||
|
global _spinner
|
||||||
|
if _spinner:
|
||||||
|
_spinner.stop()
|
||||||
|
_spinner = None
|
||||||
|
label_error(str(event.subject or "Unknown error"))
|
||||||
|
|
||||||
|
def _on_tor_bootstrapping(event):
|
||||||
|
global _spinner
|
||||||
|
_spinner = Spinner("Starting Tor...")
|
||||||
|
_spinner.start()
|
||||||
|
|
||||||
|
def _on_tor_bootstrap_progressing(event):
|
||||||
|
pct = int(event.meta.get("progress", 0))
|
||||||
|
progress_bar(pct, 100, "Tor bootstrap")
|
||||||
|
|
||||||
|
def _on_tor_bootstrapped(event):
|
||||||
|
global _spinner
|
||||||
|
if _spinner:
|
||||||
|
_spinner.stop()
|
||||||
|
_spinner = None
|
||||||
|
print()
|
||||||
|
label_ok("Tor ready")
|
||||||
|
|
||||||
|
connection_observer.subscribe('connecting', _on_connecting)
|
||||||
|
connection_observer.subscribe('connected', _on_connected)
|
||||||
|
connection_observer.subscribe('disconnected', _on_disconnected)
|
||||||
|
connection_observer.subscribe('error', _on_error)
|
||||||
|
connection_observer.subscribe('tor_bootstrapping', _on_tor_bootstrapping)
|
||||||
|
connection_observer.subscribe('tor_bootstrap_progressing', _on_tor_bootstrap_progressing)
|
||||||
|
connection_observer.subscribe('tor_bootstrapped', _on_tor_bootstrapped)
|
||||||
|
|
||||||
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'))
|
||||||
|
|
|
||||||
144
cli/ui.py
Normal file
144
cli/ui.py
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
|
||||||
|
RED = "\033[0;31m"
|
||||||
|
GREEN = "\033[0;32m"
|
||||||
|
YELLOW = "\033[0;33m"
|
||||||
|
BLUE = "\033[0;34m"
|
||||||
|
CYAN = "\033[0;36m"
|
||||||
|
WHITE = "\033[0;37m"
|
||||||
|
|
||||||
|
BRED = "\033[1;31m"
|
||||||
|
BGREEN = "\033[1;32m"
|
||||||
|
BYELLOW = "\033[1;33m"
|
||||||
|
BBLUE = "\033[1;34m"
|
||||||
|
BCYAN = "\033[1;36m"
|
||||||
|
BWHITE = "\033[1;37m"
|
||||||
|
|
||||||
|
def label_ok(msg: str):
|
||||||
|
print(f" {BGREEN}[ OK ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_warn(msg: str):
|
||||||
|
print(f" {BYELLOW}[ WARN ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_error(msg: str):
|
||||||
|
print(f" {BRED}[ ERROR]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_info(msg: str):
|
||||||
|
print(f" {BCYAN}[ INFO ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_skip(msg: str):
|
||||||
|
print(f" {DIM}[ SKIP ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_connected(msg: str):
|
||||||
|
print(f" {BGREEN}[CONNECTED ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_disconnect(msg: str):
|
||||||
|
print(f" {BRED}[DISCONNECTED]{RESET} {msg}")
|
||||||
|
|
||||||
|
def label_step(msg: str, current: int, total: int):
|
||||||
|
print(f" {BBLUE}[ {current:<2}/{total:<2} ]{RESET} {msg}")
|
||||||
|
|
||||||
|
def print_header(title: str, version: str = ""):
|
||||||
|
width = 60
|
||||||
|
line = "─" * width
|
||||||
|
ver = f"{DIM}{version}{RESET}" if version else ""
|
||||||
|
print()
|
||||||
|
print(f"{BCYAN}{line}{RESET}")
|
||||||
|
print(f"{BCYAN} {'':<56}{RESET}")
|
||||||
|
print(f"{BCYAN} {BWHITE}{title} {ver}{'':<40}{BCYAN} {RESET}")
|
||||||
|
print(f"{BCYAN} {'':<56}{RESET}")
|
||||||
|
print(f"{BCYAN}{line}{RESET}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
def print_section(label: str):
|
||||||
|
print()
|
||||||
|
print(f"{DIM}{CYAN}┌─{RESET} {BWHITE}{label}{RESET}")
|
||||||
|
|
||||||
|
def print_divider():
|
||||||
|
print(f"{DIM}{'─' * 60}{RESET}")
|
||||||
|
|
||||||
|
def progress_bar(current: int, total: int, label: str = "", width: int = 40):
|
||||||
|
filled = int(current * width / total)
|
||||||
|
empty = width - filled
|
||||||
|
bar = "#" * filled + "·" * empty
|
||||||
|
pct = int(current * 100 / total)
|
||||||
|
sys.stdout.write(
|
||||||
|
f"\r {DIM}[{RESET}{BGREEN}{bar}{RESET}{DIM}]{RESET}"
|
||||||
|
f" {BWHITE}{pct:3d}%{RESET} {DIM}{label}{RESET} "
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
def progress_simulate(steps: int = 20, label: str = "loading", delay: float = 0.04):
|
||||||
|
for i in range(1, steps + 1):
|
||||||
|
progress_bar(i, steps, label)
|
||||||
|
time.sleep(delay)
|
||||||
|
print()
|
||||||
|
|
||||||
|
class Spinner:
|
||||||
|
FRAMES = ("─", "\\", "│", "/")
|
||||||
|
|
||||||
|
def __init__(self, msg: str = "Processing..."):
|
||||||
|
self.msg = msg
|
||||||
|
self._stop_event = threading.Event()
|
||||||
|
self._thread: threading.Thread | None = None
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
self._stop_event.clear()
|
||||||
|
self._thread = threading.Thread(target=self._spin, daemon=True)
|
||||||
|
self._thread.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop(self, result_msg: str = ""):
|
||||||
|
self._stop_event.set()
|
||||||
|
if self._thread:
|
||||||
|
self._thread.join()
|
||||||
|
sys.stdout.write(f"\r{' ' * 60}\r")
|
||||||
|
sys.stdout.flush()
|
||||||
|
if result_msg:
|
||||||
|
label_ok(result_msg)
|
||||||
|
|
||||||
|
def _spin(self):
|
||||||
|
i = 0
|
||||||
|
while not self._stop_event.is_set():
|
||||||
|
frame = self.FRAMES[i % len(self.FRAMES)]
|
||||||
|
sys.stdout.write(
|
||||||
|
f"\r {BCYAN}{frame}{RESET} {DIM}{self.msg}{RESET} "
|
||||||
|
)
|
||||||
|
sys.stdout.flush()
|
||||||
|
i += 1
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *_):
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
|
||||||
|
def wait_with_spinner(msg: str, fn, *args, result_msg: str = "", **kwargs):
|
||||||
|
|
||||||
|
result_box = [None, None]
|
||||||
|
|
||||||
|
def _run():
|
||||||
|
try:
|
||||||
|
result_box[0] = fn(*args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
result_box[1] = e
|
||||||
|
|
||||||
|
s = Spinner(msg)
|
||||||
|
s.start()
|
||||||
|
t = threading.Thread(target=_run, daemon=True)
|
||||||
|
t.start()
|
||||||
|
t.join()
|
||||||
|
s.stop(result_msg)
|
||||||
|
|
||||||
|
if result_box[1] is not None:
|
||||||
|
raise result_box[1]
|
||||||
|
return result_box[0]
|
||||||
Loading…
Reference in a new issue