From 3666db25c6f49fcf210fc6c837e49fd001286b3d Mon Sep 17 00:00:00 2001 From: profitroll Date: Wed, 25 Jan 2023 14:01:13 +0100 Subject: [PATCH] Edit and remove devices from menu now --- classes/custom/messagebox.py | 10 ++ classes/custom/toplevel_dialog.py | 39 ++++++ classes/frames/devices.py | 212 ++++++++++++++++++++++++------ classes/toplevel/welcome.py | 6 +- 4 files changed, 223 insertions(+), 44 deletions(-) create mode 100644 classes/custom/messagebox.py create mode 100644 classes/custom/toplevel_dialog.py diff --git a/classes/custom/messagebox.py b/classes/custom/messagebox.py new file mode 100644 index 0000000..bb19a51 --- /dev/null +++ b/classes/custom/messagebox.py @@ -0,0 +1,10 @@ +# from typing import Union +# from ttkthemes import ThemedTk + +# from classes.custom.toplevel_dialog import ToplevelDialog + + +# def askyesno(parent: ThemedTk, title: Union[str, None], message: Union[str, None]): + +# toplevel = ToplevelDialog(parent, length=250, width=150) +# toplevel.grab_set() \ No newline at end of file diff --git a/classes/custom/toplevel_dialog.py b/classes/custom/toplevel_dialog.py new file mode 100644 index 0000000..87048ca --- /dev/null +++ b/classes/custom/toplevel_dialog.py @@ -0,0 +1,39 @@ +# import sv_ttk +# from classes.custom.themed_frame import ThemedFrame + +# from classes.custom.themed_toplevel import ThemedToplevel +# from modules.theme_titlebar import theme_title_bar +# from modules.utils import resize_window, set_icon, use_dark_mode + + +# class ToplevelDialog(ThemedToplevel): + +# def __init__(self, parent, length: int, width: int): + +# super().__init__(parent) + +# resize_window(self, length, width) + +# self.overrideredirect(True) + +# self.resizable(False, False) + +# sv_ttk.init_theme(self) + +# if use_dark_mode(): +# theme_title_bar(self, mode="dark") +# self.update() + +# set_icon(self) + +# self.focus_set() + +# self.grid_columnconfigure(0, weight=1) + +# self.window_frame = ThemedFrame(self) +# self.window_frame.grid(column=0, row=0, sticky=NSEW) + +# # self.window_frame["borderwidth"] = 1 +# # self.window_frame["relief"] = "solid" + +# self.window_frame.grid_columnconfigure(0, weight=1) \ No newline at end of file diff --git a/classes/frames/devices.py b/classes/frames/devices.py index 8cd94c8..3a0d500 100644 --- a/classes/frames/devices.py +++ b/classes/frames/devices.py @@ -1,14 +1,159 @@ from datetime import datetime from functools import partial -from tkinter import LEFT, NSEW, E, W, ttk -from tkinter.messagebox import askyesno +import platform +from tkinter import LEFT, NSEW, E, W, Misc, StringVar, ttk +from tkinter import messagebox +from tkinter.messagebox import askyesno, showinfo +from tkinter.simpledialog import askstring +from urllib.parse import quote, urlencode import requests from ttkthemes import ThemedTk from classes.custom.scrollable_frame import ScrollableFrame from classes.custom.themed_frame import ThemedFrame -from modules.utils import configGet +from modules.utils import configGet, configSet +from modules.logger import logger + + +class FrameDevice(ThemedFrame): + + def __init__(self, master: Misc, device_dict: str, **kwargs) -> None: + + super().__init__(master, style="Card.TFrame", **kwargs) + + self["borderwidth"] = 1 + self["relief"] = "solid" + + 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=("SunValleyBodyFont", 12), justify=LEFT, width=46) + 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=46) + 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"): + # button_device_rename.state(["disabled"]) + 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=("SunValleyBodyFont", 12), justify=LEFT, width=46) + 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=("SunValleyBodyFont", 12), justify=LEFT, width=46) + 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() class FrameDevices(ScrollableFrame): @@ -36,37 +181,38 @@ class FrameDevices(ScrollableFrame): i = 0 for device in self.devices: - device_frame = ThemedFrame(self, style="Card.TFrame") + device_frame = FrameDevice(self, device_dict=device) device_frame.grid(column=0, row=i, pady=9, padx=9, sticky=NSEW) # self.device_frame["borderwidth"] = 1 # self.device_frame["relief"] = "solid" - device_frame.grid_columnconfigure(0, weight=1) - device_frame.grid_columnconfigure(1, weight=3) - device_frame.grid_columnconfigure(2, weight=3) + # device_frame.grid_columnconfigure(0, weight=1) + # device_frame.grid_columnconfigure(1, weight=3) + # device_frame.grid_columnconfigure(2, weight=3) - device_title = ttk.Label(device_frame, text=device["name"], font=("SunValleyBodyFont", 12), justify=LEFT, width=46) - device_title.grid(column=0, row=0, padx=9, pady=9, sticky=W) + # device_title = ttk.Label(device_frame, text=device["name"], font=("SunValleyBodyFont", 12), justify=LEFT, width=46) + # device_title.grid(column=0, row=0, padx=9, pady=9, sticky=W) - last_upload = "N/A" if device["last_save"] == 0 else datetime.utcfromtimestamp(device["last_save"]).strftime("%d.%m.%Y %H:%M") - device_description = ttk.Label(device_frame, text=f'OS: {device["os"]}\nClient: {device["client"]}\nLast upload: {last_upload}', width=46) - device_description.grid(column=0, row=1, padx=9, pady=9, sticky=W) + # last_upload = "N/A" if device["last_save"] == 0 else datetime.utcfromtimestamp(device["last_save"]).strftime("%d.%m.%Y %H:%M") + # device_description = ttk.Label(device_frame, text=f'OS: {device["os"]}\nClient: {device["client"]}\nLast upload: {last_upload}', width=46) + # device_description.grid(column=0, row=1, padx=9, pady=9, sticky=W) - buttons_frame = ThemedFrame(device_frame) - buttons_frame.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9) - buttons_frame.grid_columnconfigure(0, weight=1) + # buttons_frame = ThemedFrame(device_frame) + # buttons_frame.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9) + # buttons_frame.grid_columnconfigure(0, weight=1) - button_device_rename = ttk.Button(buttons_frame, text="Rename", width=11) - button_device_rename.grid(column=0, row=0, padx=9, sticky=E) + # button_device_rename_action = partial(self.device_rename, device_frame, buttons_frame, device["name"]) + # button_device_rename = ttk.Button(buttons_frame, 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.device_delete, device["name"]) - button_device_delete = ttk.Button(buttons_frame, text="Delete", style="Accent.TButton", width=11, command=button_device_delete_action) - button_device_delete.grid(column=1, row=0, sticky=W) + # button_device_delete_action = partial(self.device_delete, device["name"]) + # button_device_delete = ttk.Button(buttons_frame, text="Delete", style="Accent.TButton", width=11, command=button_device_delete_action) + # button_device_delete.grid(column=1, row=0, sticky=W) - if device["name"] == configGet("name"): - # button_device_rename.state(["disabled"]) - button_device_delete.state(["disabled"]) + # if device["name"] == configGet("name"): + # # button_device_rename.state(["disabled"]) + # button_device_delete.state(["disabled"]) i += 1 @@ -75,26 +221,6 @@ class FrameDevices(ScrollableFrame): divider.grid(column=0, row=i+1, pady=9) i += 1 - def device_delete(self, name: str): - - decision = askyesno(title="Device removal", message=f"You are about to remove the device '{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/{name}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")) - - for k in range(len(self.devices)): - print(k) - if self.devices[k]["name"] == name: - del self.devices[k] - break - - for widget in self.winfo_children(): - widget.destroy() - - self.render_devices() - class FrameDevicesEmpty(ThemedFrame): def __init__(self, master: ThemedTk, **kwargs) -> None: diff --git a/classes/toplevel/welcome.py b/classes/toplevel/welcome.py index 12266d0..3d2d752 100644 --- a/classes/toplevel/welcome.py +++ b/classes/toplevel/welcome.py @@ -101,7 +101,11 @@ class ToplevelWelcome(ThemedToplevel): self.stage_entry_1.insert(0, self.address_text[:-1]) try: - requests.get(self.stage_entry_1.get()+"/check", verify=not bool(self.stage_checkbox_var.get())) + response_check = requests.get(self.stage_entry_1.get()+"/check", verify=not bool(self.stage_checkbox_var.get())) + if response_check.status_code != 200: + logger.error(f"Could not connect to '{self.stage_entry_1.get()}' because it returned {response_check.status_code}") + messagebox.showerror(title="Connection error", message=f"Server response is {response_check.status_code}. Check if your API server and it's reverse proxy (if there's one) properly configured and then try again.") + return except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema, requests.exceptions.MissingSchema) as exp: logger.error(f"Could not validate '{self.stage_entry_1.get()}' due to {exp}") messagebox.showerror(title="Invalid address", message="Address entered does not seem to be correct one. Please provide API address starting with http:// or https:// \n\nFor example:\n- https://your-api.com:8043\n- https://your-api.com")