client_update_v0.001

This commit is contained in:
2026-06-29 21:40:46 +03:00
parent fdd58724db
commit b72b15524d
2 changed files with 132 additions and 8 deletions

View File

@@ -28,7 +28,14 @@ DEFAULT_TOKEN = "change-me-client-token"
CONNECTION_TIMEOUT_SECONDS = 10
HEARTBEAT_INTERVAL_SECONDS = 10
HEARTBEAT_TIMEOUT_SECONDS = 5
DEVICE_TYPES = ("Компьютер", "Ноутбук", "Моноблок", "Тонкий клиент", "Сервер")
DEVICE_TYPES = (
"Компьютер",
"Ноутбук",
"Моноблок",
"Тонкий клиент",
"Сервер",
"Виртуальная машина",
)
CLIENT_ICON_RELATIVE = Path("picture") / "ICON" / "icon_client.png"
BG = "#c0c0c0"
@@ -506,6 +513,106 @@ def detect_os_info():
return f"{platform.system()} {platform.release()} {platform.version()} {platform.machine()}".strip()
def contains_virtual_machine_signature(value):
text = str(value or "").lower()
signatures = (
"vmware",
"virtualbox",
"virtual machine",
"virtual pc",
"virtio",
"qemu",
"kvm",
"xen",
"parallels",
"bhyve",
"bochs",
)
return any(signature in text for signature in signatures)
def contains_server_os_signature(value):
text = str(value or "").lower()
return any(
signature in text
for signature in (
"windows server",
"server edition",
"ubuntu server",
"red hat enterprise linux server",
"suse linux enterprise server",
"oracle linux server",
)
)
def detect_windows_device_type(os_info):
evidence = run_powershell(
"$cs=Get-CimInstance Win32_ComputerSystem; "
"$battery=@(Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue).Count; "
"$vmDrivers=(Get-CimInstance Win32_PnPSignedDriver -ErrorAction SilentlyContinue | "
"Where-Object { ($_.DeviceName + ' ' + $_.Manufacturer) -match "
"'VMware|VirtualBox|VirtIO|QEMU|Xen|Parallels' } | "
"Select-Object -ExpandProperty DeviceName) -join ', '; "
'"SYSTEM=$($cs.Manufacturer) $($cs.Model)`nBATTERY=$battery`nDRIVERS=$vmDrivers"',
timeout=15,
)
if contains_virtual_machine_signature(evidence):
return "Виртуальная машина"
if contains_server_os_signature(os_info):
return "Сервер"
battery_match = re.search(r"(?im)^BATTERY=(\d+)\s*$", evidence)
if battery_match and int(battery_match.group(1)) > 0:
return "Ноутбук"
return "Компьютер"
def detect_macos_device_type(os_info):
hardware = run_command(["system_profiler", "SPHardwareDataType"], timeout=15)
if contains_virtual_machine_signature(hardware):
return "Виртуальная машина"
if contains_server_os_signature(os_info):
return "Сервер"
battery = run_command(["pmset", "-g", "batt"])
if "InternalBattery" in battery and "No batteries" not in battery:
return "Ноутбук"
return "Компьютер"
def detect_linux_device_type(os_info):
evidence_parts = [run_command(["systemd-detect-virt"])]
for path in (
Path("/sys/class/dmi/id/sys_vendor"),
Path("/sys/class/dmi/id/product_name"),
Path("/sys/class/dmi/id/board_vendor"),
):
try:
evidence_parts.append(path.read_text(encoding="utf-8", errors="ignore"))
except OSError:
pass
if contains_virtual_machine_signature(" ".join(evidence_parts)):
return "Виртуальная машина"
if contains_server_os_signature(os_info):
return "Сервер"
power_supply = Path("/sys/class/power_supply")
if power_supply.exists():
for type_path in power_supply.glob("*/type"):
try:
if type_path.read_text(encoding="utf-8").strip().lower() == "battery":
return "Ноутбук"
except OSError:
pass
return "Компьютер"
def detect_device_type(os_info=""):
if is_windows():
return detect_windows_device_type(os_info)
if is_macos():
return detect_macos_device_type(os_info)
return detect_linux_device_type(os_info)
def rustdesk_config_paths():
paths = []
if is_windows():
@@ -753,11 +860,19 @@ class ClientV2App:
self.cabinet_id_by_label = {}
self.saved_cabinet_id = str(config.get("cabinet_id") or "").strip()
self.detected_payload = build_detected_payload()
self.detected_device_type = detect_device_type(self.detected_payload.get("os_info", ""))
self.device_type_is_manual = bool(config.get("device_type_manual", False))
configured_device_type = str(config.get("device_type") or "").strip()
initial_device_type = (
configured_device_type
if self.device_type_is_manual and configured_device_type
else self.detected_device_type
)
self.server_var = tk.StringVar(value=config.get("server", DEFAULT_SERVER))
self.token_var = tk.StringVar(value=config.get("token", DEFAULT_TOKEN))
self.inventory_var = tk.StringVar(value=config.get("inventory_number", ""))
self.device_type_var = tk.StringVar(value=config.get("device_type", DEVICE_TYPES[0]))
self.device_type_var = tk.StringVar(value=initial_device_type)
self.model_var = tk.StringVar(value=config.get("model", ""))
self.serial_var = tk.StringVar(value=config.get("serial_number", ""))
self.building_var = tk.StringVar(value=config.get("building", ""))
@@ -885,8 +1000,11 @@ class ClientV2App:
manual.columnconfigure(3, weight=1, uniform="manual_field")
self.add_grid_control(manual, "Инв. №", self.entry(manual, self.inventory_var), 0, 0, 1, entry=True)
self.device_type_box = self.combobox(manual, self.device_type_var, DEVICE_TYPES)
self.device_type_box.configure(state="normal")
self.add_grid_control(manual, "Тип", self.device_type_box, 0, 2, 3)
self.device_type_box.bind("<<ComboboxSelected>>", self.on_device_type_changed)
self.device_type_box.bind("<FocusOut>", self.on_device_type_changed)
self.device_type_box.bind("<Return>", self.on_device_type_changed)
self.add_grid_control(manual, "Модель", self.entry(manual, self.model_var), 1, 0, 1, entry=True)
self.add_grid_control(manual, "Серийный №", self.entry(manual, self.serial_var), 1, 2, 3, entry=True)
self.building_box = self.combobox(manual, self.building_var, ())
@@ -1132,6 +1250,7 @@ class ClientV2App:
)
def on_device_type_changed(self, _event=None):
self.device_type_is_manual = True
self.motherboard_var.set(self.motherboard_display_value())
def refresh_detected_data(self):
@@ -1288,6 +1407,7 @@ class ClientV2App:
"token": self.token_var.get().strip(),
"inventory_number": self.inventory_var.get().strip(),
"device_type": self.device_type_var.get().strip(),
"device_type_manual": self.device_type_is_manual,
"model": self.model_var.get().strip(),
"serial_number": self.serial_var.get().strip(),
"building": self.building_var.get().strip(),
@@ -1417,6 +1537,9 @@ class ClientV2App:
self.heartbeat_context = None
def start_tray(self):
if not is_windows():
self.tray_available = False
return False
try:
import pystray
from PIL import Image
@@ -1588,8 +1711,9 @@ def run_gui(args):
config["server"] = normalize_server_url(args.server)
if args.token:
config["token"] = args.token.strip()
windows_startup = bool(args.startup and is_windows())
root = tk.Tk()
if args.startup:
if windows_startup:
root.withdraw()
app = ClientV2App(root, config)
if app.is_bound_to_server:
@@ -1597,12 +1721,12 @@ def run_gui(args):
enable_windows_autostart()
except OSError as error:
app.status_var.set(f"Не удалось обновить автозагрузку: {error}")
tray_started = app.start_tray()
if args.startup and tray_started:
tray_started = app.start_tray() if is_windows() else False
if windows_startup and tray_started:
app.hide_window()
elif not tray_started:
elif is_windows() and not tray_started:
app.status_var.set("Трей недоступен: установите pystray и Pillow, затем пересоберите клиент")
if args.startup:
if windows_startup:
app.show_window()
root.mainloop()
@@ -1617,7 +1741,7 @@ def run_once(args):
payload.update(
{
"inventory_number": (args.inventory_number or "").strip(),
"device_type": args.device_type or DEVICE_TYPES[0],
"device_type": args.device_type or detect_device_type(payload.get("os_info", "")),
"model": args.model or "",
"serial_number": args.serial_number or "",
"building": args.building or "",