import platform from datetime import datetime from functools import partial from tkinter import LEFT, NSEW, E, Misc, W, messagebox, ttk from tkinter.messagebox import askyesno from urllib.parse import quote, urlencode import requests from classes.custom.themed_frame import ThemedFrame from modules.logger import logger from modules.utils import configGet, configSet, osname class FrameDevice(ThemedFrame): def __init__(self, master: Misc, device_dict: str, **kwargs) -> None: super().__init__(master, style="Card.TFrame", **kwargs) self.widget_width = 47 if osname == "nt" else 42 self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=3) self.grid_columnconfigure(2, weight=3) self.name = device_dict["name"] self.title = ttk.Label(self, text=self.name, font=("SunValleyBodyStrongFont", 12, "bold"), justify=LEFT, width=self.widget_width) self.title.grid(column=0, row=0, padx=9, pady=9, sticky=W) last_upload = "N/A" if device_dict["last_save"] == 0 else datetime.utcfromtimestamp(device_dict["last_save"]).strftime("%d.%m.%Y %H:%M") self.description = ttk.Label(self, text=f'OS: {device_dict["os"]}\nClient: {device_dict["client"]}\nLast upload: {last_upload}', width=self.widget_width) self.description.grid(column=0, row=1, padx=9, pady=9, sticky=W) self.buttons = ThemedFrame(self) self.buttons.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9) self.buttons.grid_columnconfigure(0, weight=1) self.button_device_rename_action = partial(self.rename) self.button_device_rename = ttk.Button(self.buttons, text="Rename", width=11, command=self.button_device_rename_action) self.button_device_rename.grid(column=0, row=0, padx=9, sticky=E) self.button_device_delete_action = partial(self.delete) self.button_device_delete = ttk.Button(self.buttons, text="Delete", style="Accent.TButton", width=11, command=self.button_device_delete_action) self.button_device_delete.grid(column=1, row=0, sticky=W) if self.name == configGet("name"): self.button_device_delete.state(["disabled"]) def rename(self): self.rename_entry = ttk.Entry(self, font=("SunValleyBodyFont", 12), justify=LEFT, width=27) self.rename_entry.insert(0, self.name) self.rename_entry.grid(column=0, row=0, padx=9, pady=9, sticky=W) button_device_cancel_action = partial(self.rename_cancel) button_device_cancel = ttk.Button(self.buttons, text="Cancel", width=11, command=button_device_cancel_action) button_device_cancel.grid(column=0, row=0, padx=9, sticky=E) button_device_save_action = partial(self.rename_verify) button_device_save = ttk.Button(self.buttons, text="Save", style="Accent.TButton", width=11, command=button_device_save_action) button_device_save.grid(column=1, row=0, sticky=W) def rename_verify(self): self.name_before = configGet("name") if (self.rename_entry.get().strip() == "") or ("?" in self.rename_entry.get().strip()) or ("/" in self.rename_entry.get().strip()): logger.error(f"Name {self.rename_entry.get().strip()} is not a valid name") messagebox.showerror(title="Name error", message="Provided device name is not valid. Please provide a valid one.") return try: quote(self.rename_entry.get().strip()) except: logger.error(f"Name {self.rename_entry.get().strip()} is not a valid name") messagebox.showerror(title="Name error", message="Provided device name is not valid. Please provide a valid one.") return existing_device_before = requests.get(f'{configGet("address")}/devices/{self.name}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) if existing_device_before.status_code == 200: response = requests.patch(f'{configGet("address")}/devices/{self.name}?{urlencode({"new_name": self.rename_entry.get().strip(), "os": platform.system()+" "+platform.release(), "client": f"SyncTk {self.master.master.master.master.__version__}"})}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) if response.status_code != 204: logger.error(f"Name {self.rename_entry.get().strip()} could not be set because server returned {response.status_code}") messagebox.showerror(title="Name error", message=f"Provided device name is not valid.\n\nServer response: {response.json()}") return else: logger.error(f"Tried to rename {self.name} into {self.rename_entry.get().strip()} but server returned {existing_device_before.status_code}") messagebox.showerror(title="Rename error", message="It seems like this device no longer exists.") return self.name = self.rename_entry.get().strip() for widget in self.winfo_children(): if isinstance(widget, ttk.Entry): widget.destroy() device_title = ttk.Label(self, text=self.name, font=("SunValleyBodyStrongFont", 12, "bold"), justify=LEFT, width=self.widget_width) device_title.grid(column=0, row=0, padx=9, pady=9, sticky=W) button_device_rename_action = partial(self.rename) button_device_rename = ttk.Button(self.buttons, text="Rename", width=11, command=button_device_rename_action) button_device_rename.grid(column=0, row=0, padx=9, sticky=E) button_device_delete_action = partial(self.delete) button_device_delete = ttk.Button(self.buttons, text="Delete", style="Accent.TButton", width=11, command=button_device_delete_action) button_device_delete.grid(column=1, row=0, sticky=W) if self.name_before == configGet("name"): configSet(["name"], self.name) if self.name == configGet("name"): button_device_delete.state(["disabled"]) def rename_cancel(self): for widget in self.winfo_children(): if isinstance(widget, ttk.Entry): widget.destroy() device_title = ttk.Label(self, text=self.name, font=("SunValleyBodyStrongFont", 12, "bold"), justify=LEFT, width=self.widget_width) device_title.grid(column=0, row=0, padx=9, pady=9, sticky=W) button_device_rename_action = partial(self.rename) button_device_rename = ttk.Button(self.buttons, text="Rename", width=11, command=button_device_rename_action) button_device_rename.grid(column=0, row=0, padx=9, sticky=E) button_device_delete_action = partial(self.delete) button_device_delete = ttk.Button(self.buttons, text="Delete", style="Accent.TButton", width=11, command=button_device_delete_action) button_device_delete.grid(column=1, row=0, sticky=W) if self.name == configGet("name"): button_device_delete.state(["disabled"]) def delete(self): decision = askyesno(title="Device removal", message=f"You are about to remove the device '{self.name}' and this will also remove all the save files uploaded by this device. Are you sure you want to continue?") if decision is False: return requests.delete(f'{configGet("address")}/devices/{self.name}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) for k in range(len(self.master.devices)): if self.master.devices[k]["name"] == self.name: del self.master.devices[k] break self.destroy()