WIP: refactor config UI

This commit is contained in:
Orsiris de Jong 2023-12-15 20:48:12 +01:00
parent 65d4b4fb60
commit 8874b94676
7 changed files with 62 additions and 93 deletions

View file

@ -7,12 +7,12 @@ __intname__ = "npbackup.configuration"
__author__ = "Orsiris de Jong" __author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2022-2023 NetInvent" __copyright__ = "Copyright (C) 2022-2023 NetInvent"
__license__ = "GPL-3.0-only" __license__ = "GPL-3.0-only"
__build__ = "2023121301" __build__ = "2023121501"
__version__ = "2.0.0 for npbackup 2.3.0+" __version__ = "2.0.0 for npbackup 2.3.0+"
CONF_VERSION = 2.3 CONF_VERSION = 2.3
from typing import Tuple, Optional, List, Callable, Any, Union from typing import Tuple, Optional, List, Any, Union
import sys import sys
import os import os
from copy import deepcopy from copy import deepcopy
@ -304,7 +304,6 @@ def get_repo_config(full_config: dict, repo_name: str = 'default', eval_variable
Returns a dict containing the repo config, with expanded variables Returns a dict containing the repo config, with expanded variables
and a dict containing the repo interitance status and a dict containing the repo interitance status
""" """
def inherit_group_settings(repo_config: dict, group_config: dict) -> Tuple[dict, dict]: def inherit_group_settings(repo_config: dict, group_config: dict) -> Tuple[dict, dict]:
""" """
iter over group settings, update repo_config, and produce an identical version of repo_config iter over group settings, update repo_config, and produce an identical version of repo_config
@ -332,20 +331,27 @@ def get_repo_config(full_config: dict, repo_name: str = 'default', eval_variable
# recurse into elt if elt is dict # recurse into elt if elt is dict
if isinstance(_repo_config.g(key), list): if isinstance(_repo_config.g(key), list):
merged_lists = _repo_config.g(key) + _group_config.g(key) merged_lists = _repo_config.g(key) + _group_config.g(key)
# Case where repo config already contains non list info but group config has list
elif _repo_config.g(key):
merged_lists = [_repo_config.g(key)] + _group_config.g(key)
else: else:
merged_lists = _group_config.g(key) merged_lists = _group_config.g(key)
_repo_config.s(key, merged_lists) _repo_config.s(key, merged_lists)
_config_inheritance.s(key, True) _config_inheritance.s(key, True)
else: else:
# Tricky part # repo_config may or may not already contain data
# repo_config may already contain a struct
if not _repo_config: if not _repo_config:
_repo_config = CommentedMap() _repo_config = CommentedMap()
_config_inheritance = CommentedMap() _config_inheritance = CommentedMap()
if not _repo_config.g(key): if not _repo_config.g(key):
_repo_config.s(key, value) _repo_config.s(key, value)
_config_inheritance.s(key, True) _config_inheritance.s(key, True)
# Case where repo_config contains list but group info has single str
elif isinstance(_repo_config.g(key), list) and value:
merged_lists = _repo_config.g(key) + [value]
_repo_config.s(key, merged_lists)
else: else:
# In other cases, just keep repo confg
_config_inheritance.s(key, False) _config_inheritance.s(key, False)

File diff suppressed because one or more lines are too long

View file

@ -207,7 +207,7 @@ def _gui_update_state(
) )
elif current_state is False: elif current_state is False:
window["state-button"].Update( window["state-button"].Update(
"{}: {}".format(_t("generic.too_old"), backup_tz), "{}: {}".format(_t("generic.too_old"), backup_tz.replace(microsecond=0)),
button_color=GUI_STATE_OLD_BUTTON, button_color=GUI_STATE_OLD_BUTTON,
) )
elif current_state is None: elif current_state is None:
@ -570,8 +570,7 @@ def _main_gui():
logger.info(f"Using configuration file {config_file}") logger.info(f"Using configuration file {config_file}")
full_config = npbackup.configuration.load_config(config_file) full_config = npbackup.configuration.load_config(config_file)
# TODO add a repo selector repo_config, config_inheritance = npbackup.configuration.get_repo_config(full_config)
repo_config, inherit_config = npbackup.configuration.get_repo_config(full_config)
repo_list = npbackup.configuration.get_repo_list(full_config) repo_list = npbackup.configuration.get_repo_list(full_config)
backup_destination = _t("main_gui.local_folder") backup_destination = _t("main_gui.local_folder")
@ -661,7 +660,12 @@ def _main_gui():
window["snapshot-list"].expand(True, True) window["snapshot-list"].expand(True, True)
window.read(timeout=1) window.read(timeout=1)
try:
current_state, backup_tz, snapshot_list = get_gui_data(repo_config) current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
except ValueError:
current_state = None
backup_tz = None
snapshot_list = []
_gui_update_state(window, current_state, backup_tz, snapshot_list) _gui_update_state(window, current_state, backup_tz, snapshot_list)
while True: while True:
event, values = window.read(timeout=60000) event, values = window.read(timeout=60000)
@ -671,7 +675,7 @@ def _main_gui():
if event == "-active_repo-": if event == "-active_repo-":
active_repo = values['-active_repo-'] active_repo = values['-active_repo-']
if full_config.g(f"repos.{active_repo}"): if full_config.g(f"repos.{active_repo}"):
repo_config = npbackup.configuration.get_repo_config(full_config, active_repo) repo_config, config_inheriteance = npbackup.configuration.get_repo_config(full_config, active_repo)
current_state, backup_tz, snapshot_list = get_gui_data(repo_config) current_state, backup_tz, snapshot_list = get_gui_data(repo_config)
else: else:
sg.PopupError("Repo not existent in config") sg.PopupError("Repo not existent in config")
@ -784,3 +788,7 @@ def main_gui():
except _tkinter.TclError as exc: except _tkinter.TclError as exc:
logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?') logger.critical(f'Tkinter error: "{exc}". Is this a headless server ?')
sys.exit(250) sys.exit(250)
except Exception as exc:
sg.Popup(_t("config_gui.bogus_config_file") + f': {exc}')
raise #TODO replace with logger
sys.exit(251)

View file

@ -15,11 +15,12 @@ import os
from logging import getLogger from logging import getLogger
from copy import deepcopy from copy import deepcopy
import PySimpleGUI as sg import PySimpleGUI as sg
from ruamel.yaml.comments import CommentedMap
import npbackup.configuration as configuration import npbackup.configuration as configuration
from ofunctions.misc import get_key_from_value from ofunctions.misc import get_key_from_value
from npbackup.core.i18n_helper import _t from npbackup.core.i18n_helper import _t
from npbackup.path_helper import CURRENT_EXECUTABLE from npbackup.path_helper import CURRENT_EXECUTABLE
from npbackup.core.nuitka_helper import IS_COMPILED from npbackup.customization import INHERITANCE_ICON
if os.name == "nt": if os.name == "nt":
from npbackup.windows.task import create_scheduled_task from npbackup.windows.task import create_scheduled_task
@ -108,7 +109,7 @@ def config_gui(full_config: dict, config_file: str):
""" """
if key == "backup_admin_password": if key == "backup_admin_password":
return return
if key == "repo_uri": if key in ("repo_uri", "repo_group"):
if object_type == "group": if object_type == "group":
window[key].Disabled = True window[key].Disabled = True
else: else:
@ -137,10 +138,10 @@ def config_gui(full_config: dict, config_file: str):
else: else:
window[key].Update(value) window[key].Update(value)
# Set inheritance on # Enable inheritance icon when needed
inheritance_key = f'inherited.{key}' inheritance_key = f'inherited.{key}'
if inheritance_key in window.AllKeysDict: if inheritance_key in window.AllKeysDict:
window[f'inherited.{key}'].update(visible=True if inherited else False) window[inheritance_key].update(visible=True if inherited else False)
except KeyError: except KeyError:
logger.error(f"No GUI equivalent for key {key}.") logger.error(f"No GUI equivalent for key {key}.")
@ -177,31 +178,28 @@ def config_gui(full_config: dict, config_file: str):
# First we need to clear the whole GUI to reload new values # First we need to clear the whole GUI to reload new values
for key in window.AllKeysDict: for key in window.AllKeysDict:
# We only clear config keys, wihch have '.' separator # We only clear config keys, wihch have '.' separator
if "." in str(key): if "." in str(key) and not "inherited" in str(key):
window[key]('') window[key]('')
object_type, object_name = get_object_from_combo(object_name) object_type, object_name = get_object_from_combo(object_name)
if object_type == 'repo': if object_type == 'repo':
object_config, config_inheritance = configuration.get_repo_config(full_config, object_name, eval_variables=False) object_config, config_inheritance = configuration.get_repo_config(full_config, object_name, eval_variables=False)
if object_type == 'group': if object_type == 'group':
object_config = configuration.get_group_config(full_config, object_name, eval_variables=False) object_config = configuration.get_group_config(full_config, object_name, eval_variables=False)
config_inheritance = None
# Now let's iter over the whole config object and update keys accordingly # Now let's iter over the whole config object and update keys accordingly
iter_over_config(object_config, config_inheritance, object_type, unencrypted, None) iter_over_config(object_config, config_inheritance, object_type, unencrypted, None)
def update_global_gui(full_config, unencrypted=False): def update_global_gui(full_config, unencrypted=False):
# TODO global_config = CommentedMap()
return
global_config = deepcopy(full_config)
# Only update global options gui with identified global keys # Only update global options gui with identified global keys
for key in global_config.keys(): for key in full_config.keys():
if key not in ('identity', 'global_prometheus', 'global_options'): if key in ('identity', 'global_options'):
global_config.pop(key) global_config.s(key, full_config.g(key))
print(global_config) iter_over_config(global_config, None, 'group', unencrypted, None)
iter_over_config(global_config, None, None, unencrypted, None)
def update_config_dict(values, full_config): def update_config_dict(values, full_config):
@ -239,13 +237,14 @@ def config_gui(full_config: dict, config_file: str):
return full_config return full_config
def object_layout(object_type: str = "repo") -> List[list]: def object_layout() -> List[list]:
""" """
Returns the GUI layout depending on the object type Returns the GUI layout depending on the object type
""" """
backup_col = [ backup_col = [
[ [
sg.Text(_t("config_gui.compression"), size=(40, 1)), sg.Text(_t("config_gui.compression"), size=(40, 1)),
sg.pin(sg.Image(INHERITANCE_ICON, key="inherited.backup_opts.compression", tooltip=_t("config_gui.group_inherited"))),
sg.Combo( sg.Combo(
list(combo_boxes["compression"].values()), list(combo_boxes["compression"].values()),
key="backup_opts.compression", key="backup_opts.compression",
@ -259,10 +258,12 @@ def config_gui(full_config: dict, config_file: str):
), ),
size=(40, 2), size=(40, 2),
), ),
sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.paths", tooltip=_t("config_gui.group_inherited"))),
sg.Multiline(key="backup_opts.paths", size=(48, 4)), sg.Multiline(key="backup_opts.paths", size=(48, 4)),
], ],
[ [
sg.Text(_t("config_gui.source_type"), size=(40, 1)), sg.Text(_t("config_gui.source_type"), size=(40, 1)),
sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.source_type", tooltip=_t("config_gui.group_inherited"))),
sg.Combo( sg.Combo(
list(combo_boxes["source_type"].values()), list(combo_boxes["source_type"].values()),
key="backup_opts.source_type", key="backup_opts.source_type",
@ -276,7 +277,7 @@ def config_gui(full_config: dict, config_file: str):
), ),
size=(40, 2), size=(40, 2),
), ),
sg.Text("inherited", key="inherited.backup_opts.use_fs_snapshot", visible=False), sg.pin(sg.Image(INHERITANCE_ICON, expand_x=True, expand_y=True, key="inherited.backup_opts.use_fs_snapshot", tooltip=_t("config_gui.group_inherited"))),
sg.Checkbox("", key="backup_opts.use_fs_snapshot", size=(41, 1)), sg.Checkbox("", key="backup_opts.use_fs_snapshot", size=(41, 1)),
], ],
[ [
@ -533,7 +534,7 @@ def config_gui(full_config: dict, config_file: str):
[ [
sg.Tab( sg.Tab(
_t("config_gui.backup"), _t("config_gui.backup"),
[[sg.Column(backup_col, scrollable=True, vertical_scroll_only=True)]], [[sg.Column(backup_col, scrollable=True, vertical_scroll_only=True, size=(700, 450))]],
font="helvetica 16", font="helvetica 16",
key="--tab-backup--", key="--tab-backup--",
element_justification="L", element_justification="L",
@ -542,7 +543,7 @@ def config_gui(full_config: dict, config_file: str):
[ [
sg.Tab( sg.Tab(
_t("config_gui.backup_destination"), _t("config_gui.backup_destination"),
repo_col, [[sg.Column(repo_col, scrollable=True, vertical_scroll_only=True, size=(700, 450))]],
font="helvetica 16", font="helvetica 16",
key="--tab-repo--", key="--tab-repo--",
element_justification="L", element_justification="L",
@ -590,54 +591,6 @@ def config_gui(full_config: dict, config_file: str):
sg.Input(key="identity.machine_group", size=(50, 1)), sg.Input(key="identity.machine_group", size=(50, 1)),
], ],
] ]
prometheus_col = [
[sg.Text(_t("config_gui.available_variables"))],
[
sg.Text(_t("config_gui.enable_prometheus"), size=(40, 1)),
sg.Checkbox("", key="global_prometheus.metrics", size=(41, 1)),
],
[
sg.Text(_t("config_gui.job_name"), size=(40, 1)),
sg.Input(key="global_prometheus.backup_job", size=(50, 1)),
],
[
sg.Text(_t("config_gui.metrics_destination"), size=(40, 1)),
sg.Input(key="global_prometheus.destination", size=(50, 1)),
],
[
sg.Text(_t("config_gui.no_cert_verify"), size=(40, 1)),
sg.Checkbox("", key="global_prometheus.no_cert_verify", size=(41, 1)),
],
[
sg.Text(_t("config_gui.metrics_username"), size=(40, 1)),
sg.Input(key="global_prometheus.http_username", size=(50, 1)),
],
[
sg.Text(_t("config_gui.metrics_password"), size=(40, 1)),
sg.Input(key="global_prometheus.http_password", size=(50, 1)),
],
[
sg.Text(_t("config_gui.instance"), size=(40, 1)),
sg.Input(key="global_prometheus.instance", size=(50, 1)),
],
[
sg.Text(_t("generic.group"), size=(40, 1)),
sg.Input(key="global_prometheus.group", size=(50, 1)),
],
[
sg.Text(
"{}\n({}\n{})".format(
_t("config_gui.additional_labels"),
_t("config_gui.one_per_line"),
_t("config_gui.format_equals"),
),
size=(40, 3),
),
sg.Multiline(key="global_prometheus.additional_labels", size=(48, 3)),
],
]
global_options_col = [ global_options_col = [
[sg.Text(_t("config_gui.available_variables"))], [sg.Text(_t("config_gui.available_variables"))],
[ [
@ -691,15 +644,6 @@ def config_gui(full_config: dict, config_file: str):
element_justification="L", element_justification="L",
) )
], ],
[
sg.Tab(
_t("config_gui.prometheus_config"),
prometheus_col,
font="helvetica 16",
key="--tab-global-prometheus--",
element_justification="L",
)
],
[ [
sg.Tab( sg.Tab(
_t("generic.options"), _t("generic.options"),
@ -743,8 +687,7 @@ def config_gui(full_config: dict, config_file: str):
_global_layout = [ _global_layout = [
[sg.TabGroup(tab_group_layout, enable_events=True, key="--configtabgroup--")], [sg.TabGroup(tab_group_layout, enable_events=True, key="--configtabgroup--")],
[sg.Column(buttons, element_justification="L")], [sg.Push(), sg.Column(buttons, element_justification="L")],
[sg.Button("trololo")]
] ]
return _global_layout return _global_layout

View file

@ -108,3 +108,8 @@ en:
weekly: weekly copies weekly: weekly copies
monthly: monthly copies monthly: monthly copies
yearly: yearly copies yearly: yearly copies
group_inherited: Group inherited
repo_group_config: Repo group configuration
global_config: Global config
select_object: Select configuration object

View file

@ -108,3 +108,9 @@ fr:
weekly: copies hebdomadaires weekly: copies hebdomadaires
monthly: copies mensuelles monthly: copies mensuelles
yearly: copies annuelles yearly: copies annuelles
group_inherited: Hérité du groupe
repo_group_config: Configuration de groupe de dépots
global_config: Configuration globale
select_object: Selectionner l'object à configurer

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B