GUI: WIP wizard theming

This commit is contained in:
deajan 2025-10-22 12:48:26 +02:00
parent 6c80be6d74
commit 70021916bc
12 changed files with 183 additions and 68 deletions

View file

@ -9,8 +9,8 @@ __site__ = "https://www.netperfect.fr/npbackup"
__description__ = "NetPerfect Backup Client"
__copyright__ = "Copyright (C) 2022-2025 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2025092901"
__version__ = "3.0.4"
__build__ = "2025102201"
__version__ = "3.1.0-dev"
import sys

View file

@ -7,8 +7,8 @@ __intname__ = "npbackup.configuration"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2022-2025 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "2025100401"
__version__ = "npbackup 3.0.4+"
__build__ = "2025102201"
__version__ = "npbackup 3.1.0+"
from typing import Tuple, Optional, List, Any, Union
@ -32,7 +32,7 @@ from npbackup.key_management import AES_KEY, EARLIER_AES_KEY, IS_PRIV_BUILD, get
from npbackup.__version__ import __version__ as MAX_CONF_VERSION
MIN_MIGRATABLE_CONF_VERSION = "3.0.0"
MIN_CONF_VERSION = "3.0.4"
MIN_CONF_VERSION = "3.1.0"
sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(__file__), "..")))
@ -249,6 +249,31 @@ empty_config_dict = {
"full_concurrency": False, # Allow multiple npbackup instances to run at the same time
"repo_aware_concurrency": False, # Allow multiple npbackup instances to run at the same time, but only for different repos
},
"presets": {
"adds_to_existing": True,
"replaces_existing": False,
"retention_policies": {
"gfs": {
"keep_daily": 30,
"keep_weekly": 4,
"keep_monthly": 12,
"keep_yearly": 3,
"keep_within": True,
"group_by_host": True,
"group_by_tags": True,
"group_by_paths": False,
"ntp_server": None,
"keep_tags": [],
"apply_on_tags": [],
}
},
},
"destinations": {
"default_destination": {
"repo_uri": None,
"repo_password": None,
}
},
}
@ -946,6 +971,8 @@ def _load_config_file(config_file: Path) -> Union[bool, dict]:
def load_config(config_file: Path) -> Optional[dict]:
if not isinstance(config_file, Path):
config_file = Path(config_file)
full_config = _load_config_file(config_file)
if not full_config:
return None

View file

@ -60,6 +60,7 @@ from npbackup.__version__ import version_dict, version_string
from npbackup.__debug__ import _DEBUG, _NPBACKUP_ALLOW_AUTOUPGRADE_DEBUG
from npbackup.restic_wrapper import ResticRunner
from npbackup.restic_wrapper import schema
import sv_ttk
logger = getLogger()
@ -1128,10 +1129,11 @@ def _main_gui(viewer_mode: bool):
alpha_channel=1.0,
default_button_element_size=(16, 1),
right_click_menu=right_click_menu,
finalize=True,
use_ttk_buttons=True,
)
# Auto reisze table to window size
window.finalize()
sv_ttk.set_theme("light")
# Auto resize table to window size
window["snapshot-list"].expand(True, True)
window.read(timeout=0.01)
@ -1140,7 +1142,8 @@ def _main_gui(viewer_mode: bool):
if repo_config:
try:
current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
# current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
raise TypeError # WIP
except (TypeError, ValueError):
current_state = None
backup_tz = None

View file

@ -111,7 +111,8 @@ def RoundedButton(
metadata=None,
btn_size=(None, None),
):
if button_color is None:
button_color = sg.theme_button_color()
if btn_size != (None, None):
btn_width: int = btn_size[0]
btn_height: int = btn_size[1]

View file

@ -41,6 +41,7 @@ from resources.customization import (
from npbackup.task import create_scheduled_task
from npbackup.gui.helpers import quick_close_simplegui_window
from npbackup.gui.constants import combo_boxes, byte_units
import sv_ttk
logger = getLogger()
@ -2628,6 +2629,7 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
if config_file:
window.set_title(f"Configuration - {config_file}")
# sv_ttk.set_theme("light")
while True:
event, values = window.read()
# Get object type for various delete operations
@ -2822,6 +2824,7 @@ Google Cloud storage: GOOGLE_PROJECT_ID GOOGLE_APPLICATION_CREDENTIALS\n\
tree = env_variables_tree
option_key = "env.env_variables"
# It's --ADD- and not --ADD-- since we include --ADD-*-- STYLE events
if event.startswith("--ADD-"):
icon = TREE_ICON
if "ENV-VARIABLE" in event or "ENCRYPTED-ENV-VARIABLE" in event:

View file

@ -39,7 +39,8 @@ combo_boxes = {
},
"retention_options": {
"GFS": _t("wizard_gui.retention_gfs"),
"30days": _t("wizard_gui.retention_30days"),
"14days": _t("wizard_gui.retention_14_days"),
"30days": _t("wizard_gui.retention_30_days"),
"keep_all": _t("wizard_gui.retention_keep_all"),
},
"backends": {

View file

@ -34,77 +34,85 @@ from resources.customization import (
TXT_COLOR_LDR,
SIMPLEGUI_THEME,
THEME_CHOOSER_ICON,
TREE_ICON,
)
from npbackup.gui.constants import combo_boxes, byte_units
from npbackup.core.i18n_helper import _t
from npbackup.core.i18n_helper import _t, _locale
from npbackup.gui.buttons import RoundedButton
import npbackup.configuration
import sv_ttk
CONFIG_FILE = "npbackup.conf" # WIP override via --config-file
sg.LOOK_AND_FEEL_TABLE["CLEAR"] = LOOK_AND_FEEL_TABLE["CLEAR"]
sg.LOOK_AND_FEEL_TABLE["DARK"] = LOOK_AND_FEEL_TABLE["DARK"]
sg.theme(SIMPLEGUI_THEME)
logger = getLogger()
add_source_menu = [
"-ADD-SOURCE-",
[
_t("generic.add_files"),
_t("generic.add_folder"),
_t("wizard_gui.add_system"),
_t("wizard_gui.add_hyper_v"),
_t("wizard_gui.add_kvm"),
],
]
date_options = {
"format": "%Y-%m-%d",
"default_date_m_d_y": (
datetime.now().month,
datetime.now().day,
datetime.now().year,
),
"close_when_date_chosen": True,
}
conf = npbackup.configuration.load_config(CONFIG_FILE)
if not conf:
conf = npbackup.configuration.get_default_config()
try:
retention_policies = list(conf.g("presets.retention_policies").keys())
except Exception:
retention_policies = {}
backup_paths_tree = sg.TreeData()
# retention_policies = list(combo_boxes["retention_options"].values())
wizard_layouts = {
"wizard_layout_1": [
[
sg.Text(
textwrap.fill(f"{_t('wizard_gui.select_backup_sources')}", 70),
size=(None, None),
expand_x=True,
justification="c",
textwrap.fill(f"{_t('wizard_gui.select_backup_sources')}"),
size=(40, 1),
expand_x=False,
font=("Helvetica", 16),
),
sg.Push(),
sg.ButtonMenu(
_t("generic.add"),
menu_def=add_source_menu,
key="-ADD-SOURCE-MENU-",
button_color=(TXT_COLOR_LDR, BG_COLOR_LDR),
),
],
[
sg.Input(visible=False, key="--ADD-PATHS-FILE--", enable_events=True),
sg.FilesBrowse(
"", # _t("generic.add_files"
target="--ADD-PATHS-FILE--",
key="--ADD-PATHS-FILE-BUTTON--",
# button_color=(None, sg.LOOK_AND_FEEL_TABLE[SIMPLEGUI_THEME]["BACKGROUND"])
),
sg.Input(visible=False, key="--ADD-PATHS-FOLDER--", enable_events=True),
sg.FolderBrowse(
"", # _t("generic.add_folder"),
target="--ADD-PATHS-FOLDER--",
key="--ADD-PATHS-FOLDER-BUTTON--",
# button_color=(None, sg.LOOK_AND_FEEL_TABLE[SIMPLEGUI_THEME]["BACKGROUND"])
),
sg.Button(
"", # _t("generic.add_manually"),
key="--ADD-PATHS-MANUALLY--",
border_width=0,
# button_color=(None, sg.LOOK_AND_FEEL_TABLE[SIMPLEGUI_THEME]["BACKGROUND"])
),
sg.Button(
"", # _t("generic.remove_selected"),
key="--REMOVE-PATHS--",
border_width=0,
# button_color=(None, sg.LOOK_AND_FEEL_TABLE[SIMPLEGUI_THEME]["BACKGROUND"])
),
sg.Button(
"",
key="-ADD-WINDOWS-SYSTEM-",
border_width=0,
),
sg.Button(
"",
key="-ADD-HYPERV-",
border_width=0,
),
sg.Button(
"",
key="-ADD-KVM-",
border_width=0,
sg.Text(
textwrap.fill(f"{_t('wizard_gui.select_backup_sources_description')}"),
size=(80, 2),
expand_x=False,
justification="L",
),
],
[
sg.Tree(
sg.TreeData(),
key="backup_opts.paths",
headings=[],
col0_heading=_t("config_gui.backup_sources"),
headings=["Type", "Details"],
# col0_heading=_t("config_gui.backup_sources"),
expand_x=True,
expand_y=True,
header_text_color=TXT_COLOR_LDR,
@ -126,6 +134,30 @@ wizard_layouts = {
],
],
"wizard_layout_3": [
[sg.Text(_t("wizard_gui.step_3"), font=("Helvetica", 16))],
[
sg.Input("YYYY/MM/DD", key="-FIRST-BACKUP-DATE-", size=(12, 1)),
sg.Combo(
values=[h for h in range(0, 24)],
default_value=0,
key="-FIRST-BACKUP-HOUR-",
size=(3, 1),
),
sg.Text(" : "),
sg.Combo(
values=[m for m in range(0, 60)],
default_value=0,
key="-FIRST-BACKUP-MINUTE-",
size=(3, 1),
),
],
[
sg.CalendarButton(
"Calendar", target="-FIRST-BACKUP-DATE-", key="CALENDAR", **date_options
),
],
],
"wizard_layout_4": [
[
sg.Column(
[
@ -168,18 +200,18 @@ wizard_layouts = {
),
],
],
"wizard_layout_4": [
"wizard_layout_5": [
[sg.T(_t("wizard_gui.retention_settings"), font=("Helvetica", 16))],
[
sg.Combo(
values=list(combo_boxes["retention_options"].values()),
default_value=next(iter(combo_boxes["retention_options"])),
values=retention_policies,
default_value=retention_policies[0],
key="-RETENTION-TYPE-",
enable_events=True,
)
],
],
"wizard_layout_5": [
"wizard_layout_6": [
[sg.Text(_t("wizard_gui.end_user_experience"), font=("Helvetica", 16))],
[
sg.Checkbox(
@ -189,7 +221,7 @@ wizard_layouts = {
)
],
],
"wizard_layout_6": [
"wizard_layout_7": [
[sg.Text(_t("wizard_gui.end_user_experience"), font=("Helvetica", 16))],
],
}
@ -208,7 +240,7 @@ for i in range(1, len(wizard_layouts)):
[
RoundedButton(
str(i),
button_color=("#FAFAFA", "#ADADAD"),
button_color=(TXT_COLOR_LDR, BG_COLOR_LDR),
border_width=0,
key=f"-BREADCRUMB-{i}-",
btn_size=(30, 30),
@ -249,7 +281,7 @@ wizard_layout = [
border_width=0,
),
RoundedButton(
_t("generic.start"),
_t("generic.next"),
key="-NEXT-",
button_color=(TXT_COLOR_LDR, BG_COLOR_LDR),
border_width=0,
@ -266,8 +298,8 @@ wizard_layout = [
def start_wizard():
CURRENT_THEME = SIMPLEGUI_THEME
NUMBER_OF_TABS = len(wizard_tabs) + 1
current_tab = 0
NUMBER_OF_TABS = len(wizard_tabs)
current_tab = 1
wizard = sg.Window(
"NPBackup Wizard",
layout=wizard_layout,
@ -286,7 +318,7 @@ def start_wizard():
wizard.TKroot.after(60000, _reskin_job)
def set_active_tab(active_number):
for tab_index in range(1, NUMBER_OF_TABS):
for tab_index in range(1, NUMBER_OF_TABS + 1):
if tab_index != active_number:
wizard[f"-TAB{tab_index}-"].Update(visible=False)
wizard[f"-BREADCRUMB-{tab_index}-"].Update(
@ -296,9 +328,13 @@ def start_wizard():
wizard[f"-BREADCRUMB-{active_number}-"].Update(button_color=("#3F2DCB", None))
wizard.finalize()
# Widget theming from https://github.com/rdbende/Sun-Valley-ttk-theme?tab=readme-ov-file
sv_ttk.set_theme("light")
set_active_tab(1)
while True:
event, values = wizard.read()
print(event, values)
if event == sg.WIN_CLOSED or event == _t("generic.cancel"):
break
if event == "-THEME-":
@ -337,6 +373,33 @@ def start_wizard():
set_active_tab(current_tab)
elif current_tab == 1:
break
if event == "-ADD-SOURCE-MENU-":
node = None
if values["-ADD-SOURCE-MENU-"] == _t("generic.add_files"):
sg.FileBrowse(_t("generic.add_files"), target="backup_opts.paths")
node = sg.popup_get_file("Add files clicked", no_window=True)
elif values["-ADD-SOURCE-MENU-"] == _t("generic.add_folder"):
node = sg.popup_get_folder("Add folder clicked", no_window=True)
elif values["-ADD-SOURCE-MENU-"] == _t("wizard_gui.add_system"):
sg.popup("Add Windows system clicked", keep_on_top=True)
elif values["-ADD-SOURCE-MENU-"] == _t("wizard_gui.add_hyper_v"):
sg.popup("Add Hyper-V virtual machines clicked", keep_on_top=True)
elif values["-ADD-SOURCE-MENU-"] == _t("wizard_gui.add_kvm"):
sg.popup("Add KVM virtual machines clicked", keep_on_top=True)
if node:
icon = TREE_ICON
tree = backup_paths_tree
# Check if node is ADD-PATH-FILES which can contain multiple elements separated by semicolon
if ";" in node:
for path in node.split(";"):
if tree.tree_dict.get(path):
tree.delete(path)
tree.insert("", path, path, path, icon=icon)
else:
if tree.tree_dict.get(node):
tree.delete(node)
tree.insert("", node, node, node, icon=icon)
wizard["backup_opts.paths"].update(values=tree)
wizard.close()

View file

@ -14,6 +14,7 @@ ofunctions.mailer>=1.3.0
# keep in mind that freesimplegui might higher required python version in the future
freesimplegui==5.2.0.post1
reskinner==4.0.0
sv_ttk
pillow
requests
ruamel.yaml

View file

@ -71,6 +71,7 @@ en:
bad_file: Bad file
file_does_not_exist: File does not exist
add: Add
add_files: Add files
add_folder: Add folder
add_manually: Add manually

View file

@ -71,6 +71,7 @@ fr:
bad_file: Fichier erroné
file_does_not_exist: Fichier inexistant
add: Ajouter
add_files: Ajouter fichiers
add_folder: Ajouter dossier
add_manually: Ajouter manuellement

View file

@ -17,5 +17,12 @@ en:
step_4: Review summary
step_5: Finalize setup
add_system: Add Windows system
add_hyper_v: Add Hyper-V virtual machines
add_kvm: Add KVM virtual machines
retention_gfs: GFS Retention
retention_30_days: 30 Days Retention
retention_14_days: 14 Days Retention
retention_keep_all: Keep All Backups

View file

@ -17,4 +17,11 @@ fr:
step_4: Revoir le résumé
step_5: Finaliser la configuration
add_system: Ajouter un système Windows
add_hyper_v: Ajouter des machines virtuelles Hyper-V
add_kvm: Ajouter des machines virtuelles KVM
retention_gfs: Rétention GFS
retention_30_days: Rétention de 30 jours
retention_14_days: Rétention de 14 jours
retention_keep_all: Conserver toutes les sauvegardes