294 lines
12 KiB
Python
294 lines
12 KiB
Python
import customtkinter as ctk
|
|
from api.woocommerce_api import (
|
|
load_credentials,
|
|
)
|
|
from config.encrypt_config import ConfigEncryptor
|
|
from tkinter import messagebox
|
|
from PIL import Image, ImageTk
|
|
|
|
import threading
|
|
import webbrowser
|
|
|
|
from utils.update_checker import UpdateCheckError, check_for_update, get_current_version
|
|
|
|
import os
|
|
import sys
|
|
def resource_path(relative_path):
|
|
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
|
|
try:
|
|
# PyInstaller stores files in _MEIPASS when built
|
|
base_path = sys._MEIPASS
|
|
except Exception:
|
|
base_path = os.path.abspath(".")
|
|
|
|
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
|
|
|
|
class SettingsTab:
|
|
def __init__(self, tab_parent, controller):
|
|
self.tab = ctk.CTkFrame(tab_parent)
|
|
self.tab.grid(row=0, column=0, sticky="nsew")
|
|
# Initialize an instance of ConfigEncryptor
|
|
self.config_encryptor = ConfigEncryptor() # per-user storage + legacy migration
|
|
config = self.config_encryptor.load_config()
|
|
self.credentials_list = []
|
|
if config:
|
|
self.credentials_list = self.config_encryptor.load_config().get('credentials')
|
|
self.active_credential_set = (
|
|
self.get_active_credential_set()
|
|
) # Fetch active credentials
|
|
else:
|
|
self.active_credential_set = {
|
|
'nice_name': "Default"
|
|
}
|
|
self.inputs = {}
|
|
self.setup_ui()
|
|
|
|
def get_active_credential_set(self):
|
|
"""Retrieve active credential set from saved data and convert to new format if needed."""
|
|
if isinstance(self.credentials_list, list) and all(
|
|
isinstance(cred, dict) for cred in self.credentials_list
|
|
):
|
|
for cred in self.credentials_list:
|
|
if cred.get("active", False):
|
|
return cred
|
|
return self.credentials_list[0] if self.credentials_list else None
|
|
|
|
elif isinstance(self.credentials_list, dict):
|
|
self.credentials_list = [self.convert_to_new_format(self.credentials_list)]
|
|
return self.credentials_list[0]
|
|
|
|
elif isinstance(self.credentials_list, str):
|
|
self.credentials_list = [
|
|
self.convert_to_new_format({"url": self.credentials_list})
|
|
]
|
|
return self.credentials_list[0]
|
|
|
|
return None
|
|
|
|
def convert_to_new_format(self, old_credential):
|
|
return {
|
|
"url": old_credential.get("url", ""),
|
|
"consumer_key": old_credential.get("consumer_key", ""),
|
|
"consumer_secret": old_credential.get("consumer_secret", ""),
|
|
"username": old_credential.get("username", ""),
|
|
"password": old_credential.get("password", ""),
|
|
"name": old_credential.get("name", "Default Credential Set"),
|
|
"nice_name": old_credential.get("nice_name", old_credential.get("url", "Unnamed Credential")),
|
|
"active": True,
|
|
}
|
|
|
|
def setup_ui(self):
|
|
# Dropdown to select active credentials
|
|
self.credential_var = ctk.StringVar()
|
|
self.credential_var.set(
|
|
self.active_credential_set.get("nice_name", "Default")
|
|
)
|
|
credential_options = [
|
|
cred.get("nice_name", "Unnamed Credential")
|
|
for cred in self.credentials_list
|
|
]
|
|
|
|
dropdown_label = ctk.CTkLabel(self.tab, text="Select Active Credentials:")
|
|
dropdown_label.grid(row=0, column=0, padx=5, columnspan=2 , pady=5, sticky="w")
|
|
self.credential_dropdown = ctk.CTkComboBox(
|
|
self.tab,
|
|
variable=self.credential_var,
|
|
values=credential_options,
|
|
command=self.load_selected_credential,
|
|
)
|
|
self.credential_dropdown.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="w")
|
|
|
|
# Show fields for credentials
|
|
self.create_credentials_form(self.active_credential_set, row_index=1)
|
|
|
|
icon_path = resource_path("save")
|
|
icon_image = ctk.CTkImage(light_image=Image.open(icon_path), size=(24, 24)) if icon_path else None
|
|
|
|
save_button = ctk.CTkButton(
|
|
self.tab, width=100,fg_color="green",image=icon_image, text="Save", command=self.save_credentials
|
|
)
|
|
save_button.grid(row=7, column=0, columnspan=1, pady=10)
|
|
|
|
new_button = ctk.CTkButton(
|
|
self.tab, width=100, fg_color="green", image=icon_image, text="New", command=self.add_new_credential_set
|
|
)
|
|
new_button.grid(row=7, column=1, columnspan=1, pady=10)
|
|
|
|
# Trash icon for delete button
|
|
trash_icon_path = resource_path("trash")
|
|
trash_icon_image = ctk.CTkImage(light_image=Image.open(trash_icon_path), size=(24, 24)) if trash_icon_path else None
|
|
|
|
delete_button = ctk.CTkButton(
|
|
self.tab, width=100, fg_color="red", image=trash_icon_image, text="Delete", command=self.delete_selected_credential
|
|
)
|
|
delete_button.grid(row=7, column=2, columnspan=1, pady=10)
|
|
|
|
# --- App updates ---
|
|
self._current_version = get_current_version()
|
|
self._version_var = ctk.StringVar(value=f"Version: {self._current_version}")
|
|
self._update_status_var = ctk.StringVar(value="")
|
|
|
|
version_label = ctk.CTkLabel(self.tab, textvariable=self._version_var)
|
|
version_label.grid(row=8, column=0, columnspan=2, padx=5, pady=(15, 5), sticky="w")
|
|
|
|
check_update_button = ctk.CTkButton(
|
|
self.tab,
|
|
width=140,
|
|
text="Check updates",
|
|
command=self.check_updates,
|
|
)
|
|
check_update_button.grid(row=8, column=2, columnspan=2, padx=5, pady=(15, 5), sticky="w")
|
|
|
|
update_status_label = ctk.CTkLabel(self.tab, textvariable=self._update_status_var)
|
|
update_status_label.grid(row=9, column=0, columnspan=4, padx=5, pady=(0, 10), sticky="w")
|
|
|
|
def check_updates(self):
|
|
self._update_status_var.set("Checking GitHub for updates...")
|
|
threading.Thread(target=self._check_updates_worker, daemon=True).start()
|
|
|
|
def _check_updates_worker(self):
|
|
try:
|
|
info = check_for_update("SitiWeb", "images_py", current_version=self._current_version)
|
|
except (UpdateCheckError, Exception) as exc:
|
|
self.tab.after(0, lambda: self._on_update_check_failed(str(exc)))
|
|
return
|
|
|
|
self.tab.after(0, lambda: self._on_update_check_complete(info))
|
|
|
|
def _on_update_check_failed(self, error_message: str):
|
|
self._update_status_var.set("Update check failed.")
|
|
messagebox.showerror("Update check failed", error_message)
|
|
|
|
def _on_update_check_complete(self, info):
|
|
self._update_status_var.set(f"Latest: {info.latest_tag} (current: {info.current_version})")
|
|
|
|
if not info.update_available:
|
|
messagebox.showinfo(
|
|
"No updates",
|
|
f"You're up to date.\n\nCurrent: {info.current_version}\nLatest: {info.latest_tag}",
|
|
)
|
|
return
|
|
|
|
open_release = messagebox.askyesno(
|
|
"Update available",
|
|
f"A newer version is available.\n\nCurrent: {info.current_version}\nLatest: {info.latest_tag}\n\nOpen the release page?",
|
|
)
|
|
if open_release and info.html_url:
|
|
webbrowser.open(info.html_url)
|
|
|
|
def create_credentials_form(self, credentials, row_index):
|
|
settings_options = {
|
|
"url": {
|
|
"type": "text",
|
|
"label": "WooCommerce URL:",
|
|
"default": credentials.get("url", ""),
|
|
},
|
|
"consumer_key": {
|
|
"type": "text",
|
|
"label": "Consumer Key:",
|
|
"default": credentials.get("consumer_key", ""),
|
|
},
|
|
"consumer_secret": {
|
|
"type": "text",
|
|
"label": "Consumer Secret:",
|
|
"default": credentials.get("consumer_secret", ""),
|
|
},
|
|
"username": {
|
|
"type": "text",
|
|
"label": "Username:",
|
|
"default": credentials.get("username", ""),
|
|
},
|
|
"password": {
|
|
"type": "text",
|
|
"label": "Password:",
|
|
"default": credentials.get("password", ""),
|
|
"show": "*",
|
|
},
|
|
"nice_name": {
|
|
"type": "text",
|
|
"label": "Nice Name:",
|
|
"default": credentials.get("nice_name", "Unnamed Credential"),
|
|
},
|
|
}
|
|
|
|
self.inputs = {} # Reset inputs for new credentials set
|
|
for name, details in settings_options.items():
|
|
self.create_setting(name, details, row_index)
|
|
row_index += 1
|
|
|
|
def create_setting(self, name, details, row_index):
|
|
lbl = ctk.CTkLabel(self.tab, text=details["label"])
|
|
lbl.grid(row=row_index, column=0,columnspan=2, padx=5, pady=5, sticky="w")
|
|
|
|
entry = ctk.CTkEntry(self.tab, show=details.get("show", None))
|
|
entry.insert(0, details["default"])
|
|
entry.grid(row=row_index, column=2,columnspan=2, padx=5, pady=5, sticky="w")
|
|
self.inputs[name] = entry
|
|
|
|
def load_selected_credential(self, selected_name):
|
|
for cred in self.credentials_list:
|
|
if cred.get("nice_name", "Unnamed Credential") == selected_name:
|
|
self.active_credential_set = cred
|
|
self.create_credentials_form(self.active_credential_set, row_index=1)
|
|
break
|
|
|
|
def save_credentials(self):
|
|
credentials = {
|
|
"url": self.inputs["url"].get(),
|
|
"consumer_key": self.inputs["consumer_key"].get(),
|
|
"consumer_secret": self.inputs["consumer_secret"].get(),
|
|
"username": self.inputs["username"].get(),
|
|
"password": self.inputs["password"].get(),
|
|
"name": self.inputs["nice_name"].get(),
|
|
"nice_name": self.inputs["nice_name"].get(),
|
|
"active": True,
|
|
}
|
|
|
|
ConfigEncryptor().save_credentials(credentials)
|
|
|
|
# Reload from storage to avoid duplicates and to reflect the active flag updates.
|
|
config = ConfigEncryptor().load_config() or {}
|
|
self.credentials_list = config.get("credentials", []) or []
|
|
self.credential_dropdown.configure(
|
|
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
|
|
)
|
|
self.credential_var.set(credentials.get("nice_name", "Default"))
|
|
|
|
def add_new_credential_set(self):
|
|
self.active_credential_set = {
|
|
"url": "",
|
|
"consumer_key": "",
|
|
"consumer_secret": "",
|
|
"username": "",
|
|
"password": "",
|
|
"name": "New Credential Set",
|
|
"nice_name": "New Credential Set",
|
|
"active": False,
|
|
}
|
|
self.create_credentials_form(self.active_credential_set, row_index=1)
|
|
self.credential_var.set(self.active_credential_set["nice_name"])
|
|
|
|
def delete_selected_credential(self):
|
|
selected_name = self.credential_var.get()
|
|
|
|
# Find and remove the selected credential from the list
|
|
self.credentials_list = [
|
|
cred for cred in self.credentials_list if cred.get("nice_name") != selected_name
|
|
]
|
|
|
|
# Save updated credentials list to storage
|
|
ConfigEncryptor().delete_credentials(selected_name)
|
|
|
|
# Update the dropdown and form after deletion
|
|
if self.credentials_list:
|
|
self.active_credential_set = self.credentials_list[0] # Load first available credential
|
|
self.credential_var.set(self.active_credential_set["nice_name"])
|
|
self.create_credentials_form(self.active_credential_set, row_index=1)
|
|
else:
|
|
self.active_credential_set = {}
|
|
self.create_credentials_form({}, row_index=1) # Clear form if no credentials are left
|
|
|
|
self.credential_dropdown.configure(
|
|
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
|
|
)
|