v1.9.1: PNG Material Design icons — 28 icons, dark/light theme, HiDPI, graceful Unicode fallback
- 56 PNG icons (28 unique × 2 color variants) from Material Design Icons (round style, 96×96px) - core/icons.py: ctk_icon(), make_icon_button(), reconfigure_icon_button() with CTkImage cache - Updated 15 GUI files: app.py, sidebar.py, server_dialog.py, all tabs - build.py: auto-include assets/icons/ in PyInstaller bundle, patch rollover at 99→minor+1 - tools/download_icons.py: icon download script - Automatic dark↔light theme switching via CTkImage dual-image support Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ from tkinter import messagebox, filedialog
|
||||
import customtkinter as ctk
|
||||
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, ctk_icon, make_icon_button
|
||||
from core.ssh_client import SFTPSession
|
||||
from gui.widgets.file_list import FileListWidget
|
||||
|
||||
@@ -90,28 +90,34 @@ class FilesTab(ctk.CTkFrame):
|
||||
ctk.CTkLabel(left_header, text=t("local_files"),
|
||||
font=ctk.CTkFont(size=13, weight="bold")).pack(side="left")
|
||||
|
||||
_back_img = ctk_icon("back", 16)
|
||||
self._local_back_btn = ctk.CTkButton(
|
||||
left_header, text="\u2190", width=30, height=28,
|
||||
left_header, text="" if _back_img else "\u2190",
|
||||
image=_back_img, width=30, height=28,
|
||||
command=self._local_go_back,
|
||||
)
|
||||
self._local_back_btn.pack(side="left", padx=(8, 2))
|
||||
|
||||
_up_img = ctk_icon("up", 16)
|
||||
self._local_up_btn = ctk.CTkButton(
|
||||
left_header, text="\u2191", width=30, height=28,
|
||||
left_header, text="" if _up_img else "\u2191",
|
||||
image=_up_img, width=30, height=28,
|
||||
command=self._local_go_up,
|
||||
)
|
||||
self._local_up_btn.pack(side="left", padx=2)
|
||||
|
||||
# Local refresh button
|
||||
_ref_img = ctk_icon("refresh", 16)
|
||||
self._local_refresh_btn = ctk.CTkButton(
|
||||
left_header, text="\u21BB", width=30, height=28,
|
||||
left_header, text="" if _ref_img else "\u21BB",
|
||||
image=_ref_img, width=30, height=28,
|
||||
command=self._refresh_local,
|
||||
)
|
||||
self._local_refresh_btn.pack(side="left", padx=2)
|
||||
|
||||
# Browse button
|
||||
self._browse_btn = ctk.CTkButton(
|
||||
left_header, text=icon_text("folder_open", t("browse")), width=75, height=28,
|
||||
self._browse_btn = make_icon_button(
|
||||
left_header, "folder_open", t("browse"), width=75, height=28,
|
||||
command=self._browse_local,
|
||||
)
|
||||
self._browse_btn.pack(side="left", padx=2)
|
||||
@@ -158,19 +164,22 @@ class FilesTab(ctk.CTkFrame):
|
||||
font=ctk.CTkFont(size=13, weight="bold")).pack(side="left")
|
||||
|
||||
self._remote_back_btn = ctk.CTkButton(
|
||||
right_header, text="\u2190", width=30, height=28,
|
||||
right_header, text="" if _back_img else "\u2190",
|
||||
image=_back_img, width=30, height=28,
|
||||
command=self._remote_go_back,
|
||||
)
|
||||
self._remote_back_btn.pack(side="left", padx=(8, 2))
|
||||
|
||||
self._remote_up_btn = ctk.CTkButton(
|
||||
right_header, text="\u2191", width=30, height=28,
|
||||
right_header, text="" if _up_img else "\u2191",
|
||||
image=_up_img, width=30, height=28,
|
||||
command=self._remote_go_up,
|
||||
)
|
||||
self._remote_up_btn.pack(side="left", padx=2)
|
||||
|
||||
self._remote_refresh_btn = ctk.CTkButton(
|
||||
right_header, text="\u21BB", width=30, height=28,
|
||||
right_header, text="" if _ref_img else "\u21BB",
|
||||
image=_ref_img, width=30, height=28,
|
||||
command=self._refresh_remote,
|
||||
)
|
||||
self._remote_refresh_btn.pack(side="left", padx=2)
|
||||
@@ -204,14 +213,14 @@ class FilesTab(ctk.CTkFrame):
|
||||
toolbar = ctk.CTkFrame(self, fg_color="transparent")
|
||||
toolbar.pack(fill="x", padx=10, pady=4)
|
||||
|
||||
self._upload_btn = ctk.CTkButton(
|
||||
toolbar, text=icon_text("upload", t("upload")), width=110, height=30,
|
||||
self._upload_btn = make_icon_button(
|
||||
toolbar, "upload", t("upload"), width=110, height=30,
|
||||
command=self._upload_selected,
|
||||
)
|
||||
self._upload_btn.pack(side="left", padx=(0, 4))
|
||||
|
||||
self._download_btn = ctk.CTkButton(
|
||||
toolbar, text=icon_text("download", t("download")), width=110, height=30,
|
||||
self._download_btn = make_icon_button(
|
||||
toolbar, "download", t("download"), width=110, height=30,
|
||||
command=self._download_selected,
|
||||
)
|
||||
self._download_btn.pack(side="left", padx=4)
|
||||
@@ -219,21 +228,21 @@ class FilesTab(ctk.CTkFrame):
|
||||
sep = ctk.CTkFrame(toolbar, width=2, height=24, fg_color="gray40")
|
||||
sep.pack(side="left", padx=8)
|
||||
|
||||
self._mkdir_btn = ctk.CTkButton(
|
||||
toolbar, text=icon_text("folder", t("new_folder")), width=110, height=30,
|
||||
self._mkdir_btn = make_icon_button(
|
||||
toolbar, "folder", t("new_folder"), width=110, height=30,
|
||||
command=self._mkdir_remote,
|
||||
)
|
||||
self._mkdir_btn.pack(side="left", padx=4)
|
||||
|
||||
self._delete_btn = ctk.CTkButton(
|
||||
toolbar, text=icon_text("delete", t("delete_files")), width=90, height=30,
|
||||
self._delete_btn = make_icon_button(
|
||||
toolbar, "delete", t("delete_files"), width=90, height=30,
|
||||
fg_color="#dc2626", hover_color="#b91c1c",
|
||||
command=self._delete_remote,
|
||||
)
|
||||
self._delete_btn.pack(side="left", padx=4)
|
||||
|
||||
self._rename_btn = ctk.CTkButton(
|
||||
toolbar, text=icon_text("edit", t("rename_file")), width=110, height=30,
|
||||
self._rename_btn = make_icon_button(
|
||||
toolbar, "edit", t("rename_file"), width=110, height=30,
|
||||
command=self._rename_remote,
|
||||
)
|
||||
self._rename_btn.pack(side="left", padx=4)
|
||||
|
||||
@@ -9,7 +9,7 @@ from tkinter import ttk
|
||||
import customtkinter as ctk
|
||||
from core.grafana_client import GrafanaClient
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button, reconfigure_icon_button
|
||||
from gui.tabs.query_tab import apply_dark_scrollbar_style
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ class GrafanaTab(ctk.CTkFrame):
|
||||
font=ctk.CTkFont(size=18, weight="bold"))
|
||||
title.pack(side="left")
|
||||
|
||||
self._refresh_btn = ctk.CTkButton(header_frame, text=icon_text("refresh", t("grafana_refresh")), width=110,
|
||||
command=self._refresh)
|
||||
self._refresh_btn = make_icon_button(header_frame, "refresh", t("grafana_refresh"), width=110,
|
||||
command=self._refresh)
|
||||
self._refresh_btn.pack(side="right")
|
||||
|
||||
# ── Dashboards section ──
|
||||
@@ -131,8 +131,10 @@ class GrafanaTab(ctk.CTkFrame):
|
||||
except Exception as e:
|
||||
self.after(0, lambda: self._set_status(f"(error) {e}", "#ef4444"))
|
||||
finally:
|
||||
self.after(0, lambda: self._refresh_btn.configure(
|
||||
state="normal", text=icon_text("refresh", t("grafana_refresh"))))
|
||||
self.after(0, lambda: (
|
||||
self._refresh_btn.configure(state="normal"),
|
||||
reconfigure_icon_button(self._refresh_btn, "refresh", t("grafana_refresh")),
|
||||
))
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ Info tab — display server details, edit button.
|
||||
|
||||
import customtkinter as ctk
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
|
||||
|
||||
class InfoTab(ctk.CTkFrame):
|
||||
@@ -56,7 +56,7 @@ class InfoTab(ctk.CTkFrame):
|
||||
self._fields[key] = val
|
||||
|
||||
# Edit button
|
||||
self.edit_btn = ctk.CTkButton(self, text=icon_text("edit", t("edit_server_btn")), command=self._on_edit)
|
||||
self.edit_btn = make_icon_button(self, "edit", t("edit_server_btn"), command=self._on_edit)
|
||||
self.edit_btn.pack(pady=15)
|
||||
|
||||
def set_server(self, alias: str | None):
|
||||
|
||||
@@ -7,7 +7,7 @@ import threading
|
||||
import customtkinter as ctk
|
||||
from core.ssh_client import SSHClientWrapper
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
|
||||
|
||||
class KeysTab(ctk.CTkFrame):
|
||||
@@ -30,13 +30,13 @@ class KeysTab(ctk.CTkFrame):
|
||||
btn_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
btn_frame.pack(fill="x", padx=15, pady=5)
|
||||
|
||||
self.gen_btn = ctk.CTkButton(btn_frame, text=icon_text("key", t("generate_key")), command=self._generate)
|
||||
self.gen_btn = make_icon_button(btn_frame, "key", t("generate_key"), command=self._generate)
|
||||
self.gen_btn.pack(side="left", padx=(0, 10))
|
||||
|
||||
self.install_btn = ctk.CTkButton(btn_frame, text=icon_text("upload", t("install_on_server")), fg_color="#22c55e", hover_color="#16a34a", command=self._install)
|
||||
self.install_btn = make_icon_button(btn_frame, "upload", t("install_on_server"), fg_color="#22c55e", hover_color="#16a34a", command=self._install)
|
||||
self.install_btn.pack(side="left")
|
||||
|
||||
self.copy_btn = ctk.CTkButton(btn_frame, text=icon_text("copy", t("copy_public_key")), fg_color="#6b7280", command=self._copy_key)
|
||||
self.copy_btn = make_icon_button(btn_frame, "copy", t("copy_public_key"), fg_color="#6b7280", command=self._copy_key)
|
||||
self.copy_btn.pack(side="right")
|
||||
|
||||
# Status log
|
||||
|
||||
@@ -10,7 +10,7 @@ import threading
|
||||
import customtkinter as ctk
|
||||
from core.remote_desktop import RemoteDesktopLauncher
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button, reconfigure_icon_button
|
||||
from core.logger import log
|
||||
|
||||
|
||||
@@ -33,15 +33,15 @@ class LaunchTab(ctk.CTkFrame):
|
||||
# ── Toolbar (shown when RDP connected) ──
|
||||
self._toolbar = ctk.CTkFrame(self, height=36, fg_color="transparent")
|
||||
|
||||
self._disconnect_btn = ctk.CTkButton(
|
||||
self._toolbar, text=icon_text("delete", t("rdp_disconnect")),
|
||||
self._disconnect_btn = make_icon_button(
|
||||
self._toolbar, "delete", t("rdp_disconnect"),
|
||||
width=120, height=30, fg_color="#ef4444", hover_color="#dc2626",
|
||||
command=self._disconnect,
|
||||
)
|
||||
self._disconnect_btn.pack(side="left", padx=(8, 4))
|
||||
|
||||
self._fullscreen_btn = ctk.CTkButton(
|
||||
self._toolbar, text=icon_text("launch", t("rdp_fullscreen")),
|
||||
self._fullscreen_btn = make_icon_button(
|
||||
self._toolbar, "launch", t("rdp_fullscreen"),
|
||||
width=130, height=30, fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=self._toggle_fullscreen,
|
||||
)
|
||||
@@ -135,8 +135,9 @@ class LaunchTab(ctk.CTkFrame):
|
||||
).pack(fill="x", padx=15, pady=(3, 12))
|
||||
|
||||
# Connect button
|
||||
self._connect_btn = ctk.CTkButton(
|
||||
self._settings_panel, text=icon_text("execute", t("launch_connect")),
|
||||
self._connect_btn = make_icon_button(
|
||||
self._settings_panel, "execute", t("launch_connect"),
|
||||
icon_size=20,
|
||||
font=ctk.CTkFont(size=18, weight="bold"),
|
||||
width=220, height=50,
|
||||
command=self._on_connect,
|
||||
@@ -374,9 +375,7 @@ class LaunchTab(ctk.CTkFrame):
|
||||
if self._is_fullscreen:
|
||||
# Exit fullscreen — reattach
|
||||
self._is_fullscreen = False
|
||||
self._fullscreen_btn.configure(
|
||||
text=icon_text("launch", t("rdp_fullscreen")),
|
||||
)
|
||||
reconfigure_icon_button(self._fullscreen_btn, "launch", t("rdp_fullscreen"))
|
||||
self._rdp_frame.update_idletasks()
|
||||
parent_hwnd = self._rdp_frame.winfo_id()
|
||||
w = self._rdp_frame.winfo_width()
|
||||
@@ -385,9 +384,7 @@ class LaunchTab(ctk.CTkFrame):
|
||||
else:
|
||||
# Go fullscreen — detach, then maximize after event loop settles
|
||||
self._is_fullscreen = True
|
||||
self._fullscreen_btn.configure(
|
||||
text=icon_text("back", t("rdp_exit_fullscreen")),
|
||||
)
|
||||
reconfigure_icon_button(self._fullscreen_btn, "back", t("rdp_exit_fullscreen"))
|
||||
self._embedded_rdp.detach()
|
||||
self.after(300, self._maximize_detached)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import threading
|
||||
import customtkinter as ctk
|
||||
from core.winrm_client import WinRMClient
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
|
||||
|
||||
class PowershellTab(ctk.CTkFrame):
|
||||
@@ -68,8 +68,8 @@ class PowershellTab(ctk.CTkFrame):
|
||||
self._entry.bind("<Up>", lambda e: self._history_navigate(-1))
|
||||
self._entry.bind("<Down>", lambda e: self._history_navigate(1))
|
||||
|
||||
self._exec_btn = ctk.CTkButton(
|
||||
input_row, text=icon_text("execute", t("ps_execute")), width=100,
|
||||
self._exec_btn = make_icon_button(
|
||||
input_row, "execute", t("ps_execute"), width=100,
|
||||
command=self._execute,
|
||||
)
|
||||
self._exec_btn.pack(side="right")
|
||||
|
||||
@@ -8,7 +8,7 @@ from tkinter import ttk
|
||||
import customtkinter as ctk
|
||||
from core.prometheus_client import PrometheusClient
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button, reconfigure_icon_button
|
||||
from gui.tabs.query_tab import apply_dark_scrollbar_style
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ class PrometheusTab(ctk.CTkFrame):
|
||||
self._query_entry.pack(side="left", fill="x", expand=True, padx=(0, 10))
|
||||
self._query_entry.bind("<Return>", lambda e: self._execute_query())
|
||||
|
||||
self._exec_btn = ctk.CTkButton(query_frame, text=icon_text("execute", t("prom_execute")), width=100,
|
||||
command=self._execute_query)
|
||||
self._exec_btn = make_icon_button(query_frame, "execute", t("prom_execute"), width=100,
|
||||
command=self._execute_query)
|
||||
self._exec_btn.pack(side="left")
|
||||
|
||||
# ── Query results ──
|
||||
@@ -59,8 +59,8 @@ class PrometheusTab(ctk.CTkFrame):
|
||||
font=ctk.CTkFont(size=14, weight="bold"), anchor="w")
|
||||
targets_label.pack(side="left")
|
||||
|
||||
self._refresh_btn = ctk.CTkButton(targets_header, text=icon_text("refresh", t("prom_refresh")), width=100,
|
||||
command=self._refresh_all)
|
||||
self._refresh_btn = make_icon_button(targets_header, "refresh", t("prom_refresh"), width=100,
|
||||
command=self._refresh_all)
|
||||
self._refresh_btn.pack(side="right")
|
||||
|
||||
targets_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
@@ -201,8 +201,10 @@ class PrometheusTab(ctk.CTkFrame):
|
||||
except Exception as e:
|
||||
self.after(0, lambda: self._set_status(f"(error) {e}", "#ef4444"))
|
||||
finally:
|
||||
self.after(0, lambda: self._refresh_btn.configure(
|
||||
state="normal", text=icon_text("refresh", t("prom_refresh"))))
|
||||
self.after(0, lambda: (
|
||||
self._refresh_btn.configure(state="normal"),
|
||||
reconfigure_icon_button(self._refresh_btn, "refresh", t("prom_refresh")),
|
||||
))
|
||||
|
||||
threading.Thread(target=_do, daemon=True).start()
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from tkinter import ttk, filedialog
|
||||
import customtkinter as ctk
|
||||
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
from core.sql_client import SQLClient
|
||||
|
||||
_TREE_THEME_APPLIED = False
|
||||
@@ -227,9 +227,8 @@ class QueryTab(ctk.CTkFrame):
|
||||
btn_row = ctk.CTkFrame(right_ctk, fg_color="transparent")
|
||||
btn_row.pack(fill="x", padx=8, pady=4)
|
||||
|
||||
self._exec_btn = ctk.CTkButton(
|
||||
btn_row,
|
||||
text=icon_text("execute", t("query_execute")),
|
||||
self._exec_btn = make_icon_button(
|
||||
btn_row, "execute", t("query_execute"),
|
||||
command=self._execute_query,
|
||||
width=130,
|
||||
fg_color="#2563eb",
|
||||
@@ -237,9 +236,8 @@ class QueryTab(ctk.CTkFrame):
|
||||
)
|
||||
self._exec_btn.pack(side="left", padx=(0, 6))
|
||||
|
||||
self._clear_btn = ctk.CTkButton(
|
||||
btn_row,
|
||||
text=icon_text("clear", t("query_clear")),
|
||||
self._clear_btn = make_icon_button(
|
||||
btn_row, "clear", t("query_clear"),
|
||||
command=self._clear_all,
|
||||
width=80,
|
||||
fg_color="#6b7280",
|
||||
@@ -247,9 +245,8 @@ class QueryTab(ctk.CTkFrame):
|
||||
)
|
||||
self._clear_btn.pack(side="left", padx=(0, 6))
|
||||
|
||||
self._export_btn = ctk.CTkButton(
|
||||
btn_row,
|
||||
text=icon_text("save", t("query_export_csv")),
|
||||
self._export_btn = make_icon_button(
|
||||
btn_row, "save", t("query_export_csv"),
|
||||
command=self._export_csv,
|
||||
width=110,
|
||||
fg_color="#059669",
|
||||
|
||||
@@ -6,7 +6,7 @@ import threading
|
||||
import customtkinter as ctk
|
||||
from core.redis_client import RedisClient
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
|
||||
|
||||
class RedisTab(ctk.CTkFrame):
|
||||
@@ -67,28 +67,28 @@ class RedisTab(ctk.CTkFrame):
|
||||
btn_frame = ctk.CTkFrame(self, fg_color="transparent")
|
||||
btn_frame.pack(fill="x", padx=15, pady=5)
|
||||
|
||||
self._exec_btn = ctk.CTkButton(btn_frame, text=icon_text("execute", t("redis_execute")), width=100,
|
||||
command=self._execute_command)
|
||||
self._exec_btn = make_icon_button(btn_frame, "execute", t("redis_execute"), width=100,
|
||||
command=self._execute_command)
|
||||
self._exec_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._info_btn = ctk.CTkButton(btn_frame, text=icon_text("info", "INFO"), width=80,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("INFO"))
|
||||
self._info_btn = make_icon_button(btn_frame, "info", "INFO", width=80,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("INFO"))
|
||||
self._info_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._dbsize_btn = ctk.CTkButton(btn_frame, text=icon_text("hash", "DBSIZE"), width=90,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("DBSIZE"))
|
||||
self._dbsize_btn = make_icon_button(btn_frame, "info", "DBSIZE", width=90,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("DBSIZE"))
|
||||
self._dbsize_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._scan_btn = ctk.CTkButton(btn_frame, text=icon_text("search", "SCAN"), width=80,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("SCAN 0 COUNT 100"))
|
||||
self._scan_btn = make_icon_button(btn_frame, "search", "SCAN", width=80,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=lambda: self._run_quick("SCAN 0 COUNT 100"))
|
||||
self._scan_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._clear_btn = ctk.CTkButton(btn_frame, text=icon_text("clear", t("redis_clear")), width=80,
|
||||
fg_color="#374151", hover_color="#1f2937",
|
||||
command=self._clear_output)
|
||||
self._clear_btn = make_icon_button(btn_frame, "clear", t("redis_clear"), width=80,
|
||||
fg_color="#374151", hover_color="#1f2937",
|
||||
command=self._clear_output)
|
||||
self._clear_btn.pack(side="right")
|
||||
|
||||
# ── Output console ──
|
||||
|
||||
@@ -12,7 +12,7 @@ from tkinter import ttk, filedialog
|
||||
import customtkinter as ctk
|
||||
from core.s3_client import S3Client
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
from gui.tabs.query_tab import apply_dark_scrollbar_style
|
||||
|
||||
|
||||
@@ -103,32 +103,32 @@ class S3Tab(ctk.CTkFrame):
|
||||
btn_frame = ctk.CTkFrame(header, fg_color="transparent")
|
||||
btn_frame.pack(side="right")
|
||||
|
||||
self._back_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("back", t("s3_back")), width=80,
|
||||
self._back_btn = make_icon_button(
|
||||
btn_frame, "back", t("s3_back"), width=80,
|
||||
command=self._go_back, state="disabled",
|
||||
)
|
||||
self._back_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._refresh_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("refresh", t("s3_refresh")), width=100,
|
||||
self._refresh_btn = make_icon_button(
|
||||
btn_frame, "refresh", t("s3_refresh"), width=100,
|
||||
command=self._refresh,
|
||||
)
|
||||
self._refresh_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._upload_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("upload", t("s3_upload")), width=100,
|
||||
self._upload_btn = make_icon_button(
|
||||
btn_frame, "upload", t("s3_upload"), width=100,
|
||||
command=self._upload,
|
||||
)
|
||||
self._upload_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._download_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("download", t("s3_download")), width=110,
|
||||
self._download_btn = make_icon_button(
|
||||
btn_frame, "download", t("s3_download"), width=110,
|
||||
command=self._download,
|
||||
)
|
||||
self._download_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self._delete_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("delete", t("s3_delete")), width=100,
|
||||
self._delete_btn = make_icon_button(
|
||||
btn_frame, "delete", t("s3_delete"), width=100,
|
||||
fg_color="#dc2626", hover_color="#b91c1c",
|
||||
command=self._delete,
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from tkinter import filedialog, messagebox
|
||||
import customtkinter as ctk
|
||||
from core.claude_setup import check_status, install_all, install_ssh_script, install_skill, generate_ssh_key
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button
|
||||
from core.logger import log
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ class SetupTab(ctk.CTkFrame):
|
||||
btn_frame = ctk.CTkFrame(self._scroll, fg_color="transparent")
|
||||
btn_frame.pack(fill="x", padx=20, pady=15)
|
||||
|
||||
self.install_all_btn = ctk.CTkButton(
|
||||
btn_frame, text=icon_text("confirm", t("install_everything")),
|
||||
self.install_all_btn = make_icon_button(
|
||||
btn_frame, "confirm", t("install_everything"),
|
||||
font=ctk.CTkFont(size=14, weight="bold"),
|
||||
height=40, fg_color="#22c55e", hover_color="#16a34a",
|
||||
command=self._install_all
|
||||
@@ -82,16 +82,16 @@ class SetupTab(ctk.CTkFrame):
|
||||
ind_frame = ctk.CTkFrame(btn_frame, fg_color="transparent")
|
||||
ind_frame.pack(fill="x")
|
||||
|
||||
self.ssh_py_btn = ctk.CTkButton(ind_frame, text=icon_text("confirm", t("install_ssh_py")), width=110, fg_color="#6b7280",
|
||||
self.ssh_py_btn = make_icon_button(ind_frame, "confirm", t("install_ssh_py"), width=110, fg_color="#6b7280",
|
||||
command=self._install_script)
|
||||
self.ssh_py_btn.pack(side="left", padx=(0, 5))
|
||||
self.skill_btn = ctk.CTkButton(ind_frame, text=icon_text("confirm", t("install_skill")), width=110, fg_color="#6b7280",
|
||||
self.skill_btn = make_icon_button(ind_frame, "confirm", t("install_skill"), width=110, fg_color="#6b7280",
|
||||
command=self._install_skill)
|
||||
self.skill_btn.pack(side="left", padx=5)
|
||||
self.ssh_key_btn = ctk.CTkButton(ind_frame, text=icon_text("confirm", t("install_ssh_key")), width=110, fg_color="#6b7280",
|
||||
self.ssh_key_btn = make_icon_button(ind_frame, "confirm", t("install_ssh_key"), width=110, fg_color="#6b7280",
|
||||
command=self._gen_key)
|
||||
self.ssh_key_btn.pack(side="left", padx=5)
|
||||
self.refresh_btn = ctk.CTkButton(ind_frame, text=icon_text("refresh", t("refresh")), width=90, fg_color="#3b82f6",
|
||||
self.refresh_btn = make_icon_button(ind_frame, "refresh", t("refresh"), width=90, fg_color="#3b82f6",
|
||||
command=self._refresh_status)
|
||||
self.refresh_btn.pack(side="right")
|
||||
|
||||
@@ -185,8 +185,8 @@ class SetupTab(ctk.CTkFrame):
|
||||
font=ctk.CTkFont(family="Consolas", size=11)
|
||||
)
|
||||
self._path_label.pack(side="left", fill="x", expand=True, padx=(5, 10))
|
||||
self.change_path_btn = ctk.CTkButton(
|
||||
path_row, text=icon_text("folder", t("change_path")), width=120, fg_color="#6b7280",
|
||||
self.change_path_btn = make_icon_button(
|
||||
path_row, "folder", t("change_path"), width=120, fg_color="#6b7280",
|
||||
command=self._change_config_path
|
||||
)
|
||||
self.change_path_btn.pack(side="right")
|
||||
@@ -195,8 +195,8 @@ class SetupTab(ctk.CTkFrame):
|
||||
backup_row = ctk.CTkFrame(config_frame, fg_color="transparent")
|
||||
backup_row.pack(fill="x", padx=15, pady=(5, 10))
|
||||
|
||||
self.backup_btn = ctk.CTkButton(
|
||||
backup_row, text=icon_text("save", t("backup_now")), width=120, fg_color="#3b82f6",
|
||||
self.backup_btn = make_icon_button(
|
||||
backup_row, "save", t("backup_now"), width=120, fg_color="#3b82f6",
|
||||
command=self._backup_now
|
||||
)
|
||||
self.backup_btn.pack(side="left", padx=(0, 10))
|
||||
@@ -210,8 +210,8 @@ class SetupTab(ctk.CTkFrame):
|
||||
)
|
||||
self._backup_menu.pack(side="left", padx=(0, 10))
|
||||
|
||||
self.restore_btn = ctk.CTkButton(
|
||||
backup_row, text=icon_text("refresh", t("restore")), width=100, fg_color="#ef4444", hover_color="#dc2626",
|
||||
self.restore_btn = make_icon_button(
|
||||
backup_row, "refresh", t("restore"), width=100, fg_color="#ef4444", hover_color="#dc2626",
|
||||
command=self._restore_backup
|
||||
)
|
||||
self.restore_btn.pack(side="left")
|
||||
@@ -220,26 +220,26 @@ class SetupTab(ctk.CTkFrame):
|
||||
ie_row = ctk.CTkFrame(config_frame, fg_color="transparent")
|
||||
ie_row.pack(fill="x", padx=15, pady=(0, 10))
|
||||
|
||||
self.export_config_btn = ctk.CTkButton(
|
||||
ie_row, text=icon_text("upload", t("export_config")), width=130, fg_color="#6b7280",
|
||||
self.export_config_btn = make_icon_button(
|
||||
ie_row, "upload", t("export_config"), width=130, fg_color="#6b7280",
|
||||
command=self._export_config
|
||||
)
|
||||
self.export_config_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self.import_config_btn = ctk.CTkButton(
|
||||
ie_row, text=icon_text("download", t("import_config")), width=130, fg_color="#6b7280",
|
||||
self.import_config_btn = make_icon_button(
|
||||
ie_row, "download", t("import_config"), width=130, fg_color="#6b7280",
|
||||
command=self._import_config
|
||||
)
|
||||
self.import_config_btn.pack(side="left", padx=5)
|
||||
|
||||
self.export_backup_btn = ctk.CTkButton(
|
||||
ie_row, text=icon_text("upload", t("export_backup")), width=130, fg_color="#6b7280",
|
||||
self.export_backup_btn = make_icon_button(
|
||||
ie_row, "upload", t("export_backup"), width=130, fg_color="#6b7280",
|
||||
command=self._export_backup
|
||||
)
|
||||
self.export_backup_btn.pack(side="left", padx=5)
|
||||
|
||||
self.import_backup_btn = ctk.CTkButton(
|
||||
ie_row, text=icon_text("download", t("import_backup")), width=130, fg_color="#6b7280",
|
||||
self.import_backup_btn = make_icon_button(
|
||||
ie_row, "download", t("import_backup"), width=130, fg_color="#6b7280",
|
||||
command=self._import_backup
|
||||
)
|
||||
self.import_backup_btn.pack(side="left", padx=5)
|
||||
|
||||
@@ -6,7 +6,7 @@ Live countdown, one-click copy, per-server secrets.
|
||||
import threading
|
||||
import customtkinter as ctk
|
||||
from core.i18n import t
|
||||
from core.icons import icon_text
|
||||
from core.icons import icon_text, make_icon_button, reconfigure_icon_button
|
||||
|
||||
|
||||
class TOTPTab(ctk.CTkFrame):
|
||||
@@ -81,8 +81,8 @@ class TOTPTab(ctk.CTkFrame):
|
||||
widget.bind("<Button-1>", lambda e: self._copy_code())
|
||||
|
||||
# Copy button
|
||||
self.copy_btn = ctk.CTkButton(
|
||||
self, text=icon_text("copy", t("totp_copy")), width=200, height=40,
|
||||
self.copy_btn = make_icon_button(
|
||||
self, "copy", t("totp_copy"), width=200, height=40,
|
||||
font=ctk.CTkFont(size=14),
|
||||
fg_color="#22c55e", hover_color="#16a34a",
|
||||
command=self._copy_code
|
||||
@@ -108,30 +108,30 @@ class TOTPTab(ctk.CTkFrame):
|
||||
)
|
||||
self.secret_entry.pack(side="left", fill="x", expand=True, padx=(0, 5))
|
||||
|
||||
self.show_secret_btn = ctk.CTkButton(
|
||||
entry_row, text=icon_text("eye", t("show")), width=80,
|
||||
self.show_secret_btn = make_icon_button(
|
||||
entry_row, "eye", t("show"), width=80,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=self._toggle_secret
|
||||
)
|
||||
self.show_secret_btn.pack(side="left", padx=(0, 5))
|
||||
self._secret_visible = False
|
||||
|
||||
self.save_secret_btn = ctk.CTkButton(
|
||||
entry_row, text=icon_text("confirm", t("totp_save_secret")), width=110,
|
||||
self.save_secret_btn = make_icon_button(
|
||||
entry_row, "confirm", t("totp_save_secret"), width=110,
|
||||
command=self._save_secret
|
||||
)
|
||||
self.save_secret_btn.pack(side="left", padx=(0, 5))
|
||||
|
||||
self.remove_secret_btn = ctk.CTkButton(
|
||||
entry_row, text=icon_text("delete", t("totp_remove_secret")), width=110,
|
||||
self.remove_secret_btn = make_icon_button(
|
||||
entry_row, "delete", t("totp_remove_secret"), width=110,
|
||||
fg_color="#ef4444", hover_color="#dc2626",
|
||||
command=self._remove_secret
|
||||
)
|
||||
self.remove_secret_btn.pack(side="left")
|
||||
|
||||
# Generate random secret button
|
||||
self.gen_secret_btn = ctk.CTkButton(
|
||||
secret_frame, text=icon_text("key", t("totp_generate_secret")), width=200,
|
||||
self.gen_secret_btn = make_icon_button(
|
||||
secret_frame, "key", t("totp_generate_secret"), width=200,
|
||||
fg_color="#6b7280", hover_color="#4b5563",
|
||||
command=self._generate_secret
|
||||
)
|
||||
@@ -267,8 +267,8 @@ class TOTPTab(ctk.CTkFrame):
|
||||
def _toggle_secret(self):
|
||||
self._secret_visible = not self._secret_visible
|
||||
self.secret_entry.configure(show="" if self._secret_visible else "*")
|
||||
self.show_secret_btn.configure(
|
||||
text=icon_text("eye", t("hide") if self._secret_visible else t("show"))
|
||||
reconfigure_icon_button(
|
||||
self.show_secret_btn, "eye", t("hide") if self._secret_visible else t("show")
|
||||
)
|
||||
|
||||
def _save_secret(self):
|
||||
@@ -331,12 +331,12 @@ class TOTPTab(ctk.CTkFrame):
|
||||
def update_language(self):
|
||||
self.title_label.configure(text=t("totp_title"))
|
||||
self.desc_label.configure(text=t("totp_desc"))
|
||||
self.copy_btn.configure(text=icon_text("copy", t("totp_copy")))
|
||||
self.save_secret_btn.configure(text=icon_text("confirm", t("totp_save_secret")))
|
||||
self.remove_secret_btn.configure(text=icon_text("delete", t("totp_remove_secret")))
|
||||
self.gen_secret_btn.configure(text=icon_text("key", t("totp_generate_secret")))
|
||||
self.show_secret_btn.configure(
|
||||
text=icon_text("eye", t("hide") if self._secret_visible else t("show"))
|
||||
reconfigure_icon_button(self.copy_btn, "copy", t("totp_copy"))
|
||||
reconfigure_icon_button(self.save_secret_btn, "confirm", t("totp_save_secret"))
|
||||
reconfigure_icon_button(self.remove_secret_btn, "delete", t("totp_remove_secret"))
|
||||
reconfigure_icon_button(self.gen_secret_btn, "key", t("totp_generate_secret"))
|
||||
reconfigure_icon_button(
|
||||
self.show_secret_btn, "eye", t("hide") if self._secret_visible else t("show")
|
||||
)
|
||||
if not self._current_alias:
|
||||
self.server_label.configure(text=t("no_server_selected"))
|
||||
|
||||
Reference in New Issue
Block a user